summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-05-06 12:27:51 +0200
committerChristophe Simonis <chs@odoo.com>2019-05-06 12:27:51 +0200
commita97037c6d442f186a798322a6eff586a3a118ef3 (patch)
tree930cf70045eb8ec16323ccab3c4f6023aedb83bb /addons
parente5fc29564532e3c11a48b8c98a69675580d2c572 (diff)
parent78b3b650b0ddcb5fb53fda505e9e61510a1364fc (diff)
[MERGE] forward port branch saas-12.2 up to 78b3b650b0d
Diffstat (limited to 'addons')
-rw-r--r--addons/account/__init__.py2
-rw-r--r--addons/account/models/account_invoice.py22
-rw-r--r--addons/account/models/account_move.py2
-rw-r--r--addons/account/models/account_payment.py24
-rw-r--r--addons/account/models/res_users.py4
-rw-r--r--addons/account/security/account_security.xml11
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_renderer.js6
-rw-r--r--addons/account/static/src/scss/account_journal_dashboard.scss9
-rw-r--r--addons/account/static/tests/reconciliation_tests.js9
-rw-r--r--addons/account/views/account_invoice_view.xml4
-rw-r--r--addons/account/views/account_portal_templates.xml2
-rw-r--r--addons/account/views/report_payment_receipt_templates.xml2
-rw-r--r--addons/account_cancel/views/account_views.xml4
-rw-r--r--addons/auth_signup/controllers/main.py4
-rw-r--r--addons/base_import_module/models/ir_module.py6
-rw-r--r--addons/base_setup/data/base_setup_data.xml2
-rw-r--r--addons/board/static/src/js/add_to_board_menu.js12
-rw-r--r--addons/board/static/src/js/board_view.js3
-rw-r--r--addons/board/static/tests/dashboard_tests.js82
-rw-r--r--addons/calendar/data/mail_data.xml3
-rw-r--r--addons/calendar/models/calendar.py5
-rw-r--r--addons/calendar/models/mail_message.py3
-rw-r--r--addons/crm/data/crm_data.xml6
-rw-r--r--addons/crm/data/crm_demo.xml2
-rw-r--r--addons/delivery/models/sale_order.py8
-rw-r--r--addons/delivery/views/delivery_view.xml2
-rw-r--r--addons/delivery/wizard/choose_delivery_carrier.py2
-rw-r--r--addons/event/controllers/main.py2
-rw-r--r--addons/event_sale/models/sale_order.py4
-rw-r--r--addons/hr/views/hr_views.xml7
-rw-r--r--addons/hr_expense/models/hr_expense.py2
-rw-r--r--addons/hr_holidays/models/hr_leave.py15
-rw-r--r--addons/hr_holidays/models/hr_leave_allocation.py28
-rw-r--r--addons/hr_holidays/tests/test_leave_requests.py23
-rw-r--r--addons/hr_presence/views/hr_employee_views.xml2
-rw-r--r--addons/hw_screen/static/src/js/worker.js9
-rw-r--r--addons/l10n_ca/data/account_tax_data.xml2
-rw-r--r--addons/l10n_ch/models/mail_template.py7
-rw-r--r--addons/l10n_de_skr03/data/account.account.template.csv10
-rw-r--r--addons/l10n_de_skr03/data/account_tax_fiscal_position_data.xml247
-rw-r--r--addons/l10n_de_skr04/data/account.account.template.csv9
-rw-r--r--addons/l10n_de_skr04/data/account_tax_fiscal_position_data.xml279
-rw-r--r--addons/l10n_fr_pos_cert/models/__init__.py1
-rw-r--r--addons/l10n_fr_pos_cert/models/account_fiscal_position.py19
-rw-r--r--addons/l10n_ua/__init__.py3
-rw-r--r--addons/l10n_ua/__manifest__.py25
-rw-r--r--addons/l10n_ua/data/account.account.template.csv406
-rw-r--r--addons/l10n_ua/data/account_account_tag_data.xml9
-rw-r--r--addons/l10n_ua/data/account_chart_template.xml21
-rw-r--r--addons/l10n_ua/data/account_chart_template_config.xml36
-rw-r--r--addons/l10n_ua/data/account_tax_group_data.xml20
-rw-r--r--addons/l10n_ua/data/account_tax_tag_data.xml25
-rw-r--r--addons/l10n_ua/data/account_tax_template.xml485
-rw-r--r--addons/l10n_ua/static/description/icon.pngbin0 -> 628 bytes
-rw-r--r--addons/lunch/report/lunch_product_report.py4
-rw-r--r--addons/mail/models/ir_attachment.py2
-rw-r--r--addons/mail/models/ir_model.py18
-rw-r--r--addons/mail/models/mail_channel.py2
-rw-r--r--addons/mail/models/mail_template.py10
-rw-r--r--addons/mrp/models/mrp_workorder.py3
-rw-r--r--addons/mrp/models/res_config_settings.py11
-rw-r--r--addons/mrp/models/stock_rule.py6
-rw-r--r--addons/mrp/models/stock_warehouse.py5
-rw-r--r--addons/payment_authorize/controllers/main.py8
-rw-r--r--addons/payment_payulatam/models/payment.py8
-rw-r--r--addons/point_of_sale/data/point_of_sale_data.xml5
-rw-r--r--addons/point_of_sale/models/pos_order.py9
-rw-r--r--addons/point_of_sale/static/src/js/models.js2
-rw-r--r--addons/point_of_sale/static/src/js/screens.js122
-rw-r--r--addons/point_of_sale/static/src/xml/pos.xml5
-rw-r--r--addons/point_of_sale/views/account_journal_view.xml3
-rw-r--r--addons/portal/controllers/mail.py2
-rw-r--r--addons/portal/views/portal_templates.xml6
-rw-r--r--addons/product/models/product_template.py3
-rw-r--r--addons/project/models/project.py4
-rw-r--r--addons/purchase/models/account_invoice.py15
-rw-r--r--addons/purchase/report/purchase_bill.py2
-rw-r--r--addons/purchase/views/purchase_views.xml4
-rw-r--r--addons/purchase_stock/models/account_invoice.py10
-rw-r--r--addons/purchase_stock/models/stock.py8
-rw-r--r--addons/purchase_stock/tests/test_anglo_saxon_valuation_reconciliation.py13
-rw-r--r--addons/purchase_stock/tests/test_stockvaluation.py688
-rw-r--r--addons/resource/security/resource_security.xml8
-rw-r--r--addons/sale/__manifest__.py1
-rw-r--r--addons/sale/models/sale.py15
-rw-r--r--addons/sale/views/sale_portal_templates.xml65
-rw-r--r--addons/sale/views/sale_views.xml2
-rw-r--r--addons/sale_management/controllers/portal.py40
-rw-r--r--addons/sale_management/static/src/js/sale_management.js149
-rw-r--r--addons/sale_management/tests/test_sale_ui.py2
-rw-r--r--addons/sale_quotation_builder/views/sale_order_views.xml7
-rw-r--r--addons/sale_timesheet/models/account.py8
-rw-r--r--addons/sale_timesheet/report/project_profitability_report_analysis.py53
-rw-r--r--addons/sale_timesheet/security/sale_timesheet_security.xml11
-rw-r--r--addons/stock/models/stock_move.py19
-rw-r--r--addons/stock/models/stock_move_line.py10
-rw-r--r--addons/stock/models/stock_picking.py9
-rw-r--r--addons/stock/models/stock_rule.py13
-rw-r--r--addons/stock/models/stock_traceability.py7
-rw-r--r--addons/stock/models/stock_warehouse.py18
-rw-r--r--addons/stock/security/stock_security.xml2
-rw-r--r--addons/stock/static/src/xml/stock_traceability_report_backend.xml2
-rw-r--r--addons/stock/views/stock_move_views.xml34
-rw-r--r--addons/stock/views/stock_picking_views.xml13
-rw-r--r--addons/stock_account/models/product.py41
-rw-r--r--addons/stock_account/models/stock.py5
-rw-r--r--addons/stock_dropshipping/models/purchase.py12
-rw-r--r--addons/stock_dropshipping/tests/test_dropship.py17
-rw-r--r--addons/survey/models/survey_survey.py3
-rw-r--r--addons/survey/views/survey_templates.xml22
-rw-r--r--addons/theme_bootswatch/static/lib/bootswatch/cyborg/_variables.scss2
-rw-r--r--addons/web/doc/module.rst2
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js15
-rw-r--r--addons/web/static/src/js/public/public_root.js4
-rw-r--r--addons/web/static/src/js/tools/debug_manager.js2
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js13
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_model.js3
-rw-r--r--addons/web/static/src/js/widgets/date_picker.js11
-rw-r--r--addons/web/static/src/scss/bootstrap_overridden.scss2
-rw-r--r--addons/web/static/src/scss/fonts.scss30
-rw-r--r--addons/web/static/src/scss/layout_clean.scss2
-rw-r--r--addons/web/static/tests/views/calendar_tests.js63
-rw-r--r--addons/web/static/tests/views/kanban_model_tests.js64
-rw-r--r--addons/web/static/tests/views/list_tests.js44
-rw-r--r--addons/web/static/tests/views/search_view_tests.js31
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/plugin/bullet.js5
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/plugin/helper.js83
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/plugin/keyboard.js1
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/plugin/text.js2
-rw-r--r--addons/web_editor/static/src/js/wysiwyg_snippets/snippets.editor.js2
-rw-r--r--addons/web_editor/static/src/scss/bootstrap_overridden.scss2
-rw-r--r--addons/web_editor/static/tests/wysiwyg_keyboard_tests.js160
-rw-r--r--addons/web_editor/static/tests/wysiwyg_tests.js411
-rw-r--r--addons/web_unsplash/static/src/js/unsplash_image_widget.js3
-rw-r--r--addons/website/models/res_users.py6
-rw-r--r--addons/website/static/src/js/content/snippets.animation.js2
-rw-r--r--addons/website/static/src/scss/primary_variables.scss2
-rw-r--r--addons/website/views/website_navbar_templates.xml2
-rw-r--r--addons/website_blog/static/src/scss/primary_variables.scss2
-rw-r--r--addons/website_blog/static/src/scss/website_blog.scss29
-rw-r--r--addons/website_blog/views/website_blog_templates.xml8
-rw-r--r--addons/website_event_sale/models/sale_order.py2
-rw-r--r--addons/website_forum/models/forum.py3
-rw-r--r--addons/website_forum/static/src/scss/website_forum.scss5
-rw-r--r--addons/website_forum/views/website_forum.xml8
-rw-r--r--addons/website_hr_recruitment/models/hr_recruitment.py2
-rw-r--r--addons/website_livechat/models/__init__.py3
-rw-r--r--addons/website_livechat/models/ir_http.py14
-rw-r--r--addons/website_livechat/views/website_livechat.xml28
-rw-r--r--addons/website_profile/static/src/scss/website_profile.scss28
-rw-r--r--addons/website_profile/views/website_profile.xml28
-rw-r--r--addons/website_sale/controllers/main.py18
-rw-r--r--addons/website_sale/models/sale_order.py3
-rw-r--r--addons/website_sale/static/src/scss/website_sale.scss4
-rw-r--r--addons/website_sale/tests/test_website_sale_product_attribute_value_config.py4
-rw-r--r--addons/website_sale/views/templates.xml2
-rw-r--r--addons/website_sale_comparison/static/src/js/website_sale_tour_comparison.js246
-rw-r--r--addons/website_sale_comparison/tests/test_website_sale_comparison.py7
-rw-r--r--addons/website_sale_comparison/views/website_sale_comparison_template.xml1
-rw-r--r--addons/website_sale_slides/__manifest__.py6
-rw-r--r--addons/website_sale_slides/views/website_slides_templates.xml25
-rw-r--r--addons/website_slides/models/slide_channel.py3
-rw-r--r--addons/website_slides/static/src/js/slides_course_fullscreen_player.js1
-rw-r--r--addons/website_slides/static/src/js/slides_course_join.js2
-rw-r--r--addons/website_slides/static/src/js/slides_course_quiz.js1
-rw-r--r--addons/website_slides/static/src/scss/slides_slide_fullscreen.scss23
-rw-r--r--addons/website_slides/static/src/scss/website_slides.scss55
-rw-r--r--addons/website_slides/static/src/xml/slide_quiz.xml7
-rw-r--r--addons/website_slides/views/website_slides_templates_course.xml83
-rw-r--r--addons/website_slides/views/website_slides_templates_homepage.xml62
-rw-r--r--addons/website_slides/views/website_slides_templates_lesson.xml8
-rw-r--r--addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml1
-rw-r--r--addons/website_slides/views/website_slides_templates_profile.xml6
-rw-r--r--addons/website_slides_forum/views/website_slides_forum_templates.xml2
-rw-r--r--addons/website_slides_survey/models/slide_slide.py5
-rw-r--r--addons/website_slides_survey/static/src/img/certification.svg1
-rw-r--r--addons/website_slides_survey/static/src/js/slides_course_fullscreen_player.js10
-rw-r--r--addons/website_slides_survey/static/src/scss/website_profile.scss26
-rw-r--r--addons/website_slides_survey/static/src/scss/website_slides_survey.scss34
-rw-r--r--addons/website_slides_survey/views/assets.xml4
-rw-r--r--addons/website_slides_survey/views/survey_templates.xml6
-rw-r--r--addons/website_slides_survey/views/website_slides_templates_course.xml17
-rw-r--r--addons/website_slides_survey/views/website_slides_templates_homepage.xml13
-rw-r--r--addons/website_survey/views/website_survey_templates.xml4
184 files changed, 4661 insertions, 661 deletions
diff --git a/addons/account/__init__.py b/addons/account/__init__.py
index 866c39de02e..86d81923e02 100644
--- a/addons/account/__init__.py
+++ b/addons/account/__init__.py
@@ -45,6 +45,8 @@ def _auto_install_l10n(cr, registry):
'PL', 'PT', 'RO', 'SI', 'TR', 'UK', 'VE', 'VN'
]:
module_list.append('base_vat')
+ if country_code == 'MX':
+ module_list.append('l10n_mx_edi')
# European countries will be using SEPA
europe = env.ref('base.europe', raise_if_not_found=False)
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index f2a5e0d785b..8d45a4518c2 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -70,6 +70,12 @@ class AccountInvoice(models.Model):
self.amount_total_signed = self.amount_total * sign
self.amount_untaxed_signed = amount_untaxed_signed * sign
+ def _compute_sign_taxes(self):
+ for invoice in self:
+ sign = invoice.type in ['in_refund', 'out_refund'] and -1 or 1
+ invoice.amount_untaxed_invoice_signed = invoice.amount_untaxed * sign
+ invoice.amount_tax_signed = invoice.amount_tax * sign
+
@api.onchange('amount_total')
def _onchange_amount_total(self):
for inv in self:
@@ -347,8 +353,12 @@ class AccountInvoice(models.Model):
store=True, readonly=True, compute='_compute_amount', tracking=True)
amount_untaxed_signed = fields.Monetary(string='Untaxed Amount in Company Currency', currency_field='company_currency_id',
store=True, readonly=True, compute='_compute_amount')
+ amount_untaxed_invoice_signed = fields.Monetary(string='Untaxed Amount in Invoice Currency', currency_field='currency_id',
+ readonly=True, compute='_compute_sign_taxes')
amount_tax = fields.Monetary(string='Tax',
store=True, readonly=True, compute='_compute_amount')
+ amount_tax_signed = fields.Monetary(string='Tax in Invoice Currency', currency_field='currency_id',
+ readonly=True, compute='_compute_sign_taxes')
amount_total = fields.Monetary(string='Total',
store=True, readonly=True, compute='_compute_amount')
amount_total_signed = fields.Monetary(string='Total in Invoice Currency', currency_field='currency_id',
@@ -785,7 +795,7 @@ class AccountInvoice(models.Model):
# Subscribe internal users on the newly created bill
partners = self.env['res.partner'].browse(seen_partner_ids)
is_internal = lambda p: (p.user_ids and
- all(p.user_ids.mapped(lambda u: u.user_has_groups('base.group_user'))))
+ all(p.user_ids.mapped(lambda u: u.has_group('base.group_user'))))
partners_to_subscribe = partners.filtered(is_internal)
if partners_to_subscribe:
invoice.message_subscribe([p.id for p in partners_to_subscribe])
@@ -1840,14 +1850,14 @@ class AccountInvoiceLine(models.Model):
def _set_taxes(self):
""" Used in on_change to set taxes and price"""
self.ensure_one()
- if self.invoice_id.type in ('out_invoice', 'out_refund'):
- taxes = self.product_id.taxes_id or self.account_id.tax_ids or self.invoice_id.company_id.account_sale_tax_id
- else:
- taxes = self.product_id.supplier_taxes_id or self.account_id.tax_ids or self.invoice_id.company_id.account_purchase_tax_id
# Keep only taxes of the company
company_id = self.company_id or self.env.user.company_id
- taxes = taxes.filtered(lambda r: r.company_id == company_id)
+
+ if self.invoice_id.type in ('out_invoice', 'out_refund'):
+ taxes = self.product_id.taxes_id.filtered(lambda r: r.company_id == company_id) or self.account_id.tax_ids or self.invoice_id.company_id.account_sale_tax_id
+ else:
+ taxes = self.product_id.supplier_taxes_id.filtered(lambda r: r.company_id == company_id) or self.account_id.tax_ids or self.invoice_id.company_id.account_purchase_tax_id
self.invoice_line_tax_ids = fp_taxes = self.invoice_id.fiscal_position_id.map_tax(taxes, self.product_id, self.invoice_id.partner_id)
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index a32bdb0b818..40526a35e70 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -1060,7 +1060,7 @@ class AccountMoveLine(models.Model):
partner_id = self.env['res.partner']._find_accounting_partner(self[0].partner_id).id
company_currency = self[0].account_id.company_id.currency_id
- writeoff_currency = self[0].currency_id or company_currency
+ writeoff_currency = self[0].account_id.currency_id or company_currency
line_to_reconcile = self.env['account.move.line']
# Iterate and create one writeoff by journal
writeoff_moves = self.env['account.move']
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index baca985def8..405abd5c3ce 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -108,6 +108,16 @@ class account_payment(models.Model):
# Check all invoices are open
if any(invoice.state != 'open' for invoice in invoices):
raise UserError(_("You can only register payments for open invoices"))
+ # Check if, in batch payments, there are not negative invoices and positive invoices
+ dtype = invoices[0].type
+ for inv in invoices[1:]:
+ if inv.type != dtype:
+ if ((dtype == 'in_refund' and inv.type == 'in_invoice') or
+ (dtype == 'in_invoice' and inv.type == 'in_refund')):
+ raise UserError(_("You cannot register payments for vendor bills and supplier refunds at the same time."))
+ if ((dtype == 'out_refund' and inv.type == 'out_invoice') or
+ (dtype == 'out_invoice' and inv.type == 'out_refund')):
+ raise UserError(_("You cannot register payments for customer invoices and credit notes at the same time."))
amount = self._compute_payment_amount(invoices, invoices[0].currency_id)
rec.update({
@@ -660,6 +670,20 @@ class account_payment(models.Model):
return vals
+ def _get_invoice_payment_amount(self, inv):
+ """
+ Computes the amount covered by the current payment in the given invoice.
+
+ :param inv: an invoice object
+ :returns: the amount covered by the payment in the invoice
+ """
+ self.ensure_one()
+ return sum([
+ data['amount']
+ for data in inv._get_payments_vals()
+ if data['account_payment_id'] == self.id
+ ])
+
class payment_register(models.TransientModel):
_name = 'account.payment.register'
diff --git a/addons/account/models/res_users.py b/addons/account/models/res_users.py
index ab85e359d22..8030423e74d 100644
--- a/addons/account/models/res_users.py
+++ b/addons/account/models/res_users.py
@@ -14,8 +14,8 @@ class Users(models.Model):
super(Users, self)._check_one_user_type()
users_with_both_groups = self.filtered(lambda user:
- user.user_has_groups('account.group_show_line_subtotals_tax_included') and
- user.user_has_groups('account.group_show_line_subtotals_tax_excluded')
+ user.has_group('account.group_show_line_subtotals_tax_included') and
+ user.has_group('account.group_show_line_subtotals_tax_excluded')
)
if users_with_both_groups:
names = ", ".join(users_with_both_groups.mapped('name'))
diff --git a/addons/account/security/account_security.xml b/addons/account/security/account_security.xml
index bad495b9290..10fdb27f620 100644
--- a/addons/account/security/account_security.xml
+++ b/addons/account/security/account_security.xml
@@ -64,6 +64,17 @@
<field name="category_id" ref="base.module_category_hidden"/>
</record>
+ <record id="account_analytic_line_rule_billing_user" model="ir.rule">
+ <field name="name">account.analytic.line.billing.user</field>
+ <field name="model_id" ref="analytic.model_account_analytic_line"/>
+ <field name="domain_force">[(1, '=', 1)]</field>
+ <field name="groups" eval="[(4, ref('account.group_account_invoice'))]"/>
+ <field name="perm_create" eval="1"/>
+ <field name="perm_write" eval="1"/>
+ <field name="perm_unlink" eval="1"/>
+ <field name="perm_read" eval="0"/>
+ </record>
+
</data>
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
index 33b4430ab2f..b59b99bc881 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
@@ -277,6 +277,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
'field_changed': '_onFieldChanged',
}),
_avoidFieldUpdate: {},
+ MV_LINE_DEBOUNCE: 200,
/**
* create partner_id field in editable mode
@@ -289,6 +290,11 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
this.model = model;
this._initialState = state;
+ if (this.MV_LINE_DEBOUNCE) {
+ this._onSelectMoveLine = _.debounce(this._onSelectMoveLine, this.MV_LINE_DEBOUNCE, true);
+ } else {
+ this._onSelectMoveLine = this._onSelectMoveLine;
+ }
},
/**
* @override
diff --git a/addons/account/static/src/scss/account_journal_dashboard.scss b/addons/account/static/src/scss/account_journal_dashboard.scss
index 711021fbbad..b842a94f058 100644
--- a/addons/account/static/src/scss/account_journal_dashboard.scss
+++ b/addons/account/static/src/scss/account_journal_dashboard.scss
@@ -41,6 +41,15 @@
@include o-position-absolute($bottom: 25%, $right: 0);
z-index: 1;
}
+ .o_kanban_group {
+ &:not(.o_column_folded) {
+ width: 450px + 2*$o-kanban-group-padding;
+
+ @include media-breakpoint-down(sm) {
+ width: 100%;
+ }
+ }
+ }
}
// Style for the widget "dashboard_graph"
diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js
index 5e8d71b466d..0248d0cec7e 100644
--- a/addons/account/static/tests/reconciliation_tests.js
+++ b/addons/account/static/tests/reconciliation_tests.js
@@ -585,6 +585,7 @@ odoo.define('account.reconciliation_tests', function (require) {
"use strict";
var ReconciliationClientAction = require('account.ReconciliationClientAction');
+var ReconciliationRenderer = require('account.ReconciliationRenderer');
var demoData = require('account.reconciliation_tests.data');
var testUtils = require('web.test_utils');
@@ -592,7 +593,13 @@ var testUtils = require('web.test_utils');
QUnit.module('account', {
beforeEach: function () {
this.params = demoData.getParams();
- }
+ testUtils.patch(ReconciliationRenderer.LineRenderer, {
+ MV_LINE_DEBOUNCE: 0,
+ });
+ },
+ afterEach: function () {
+ testUtils.unpatch(ReconciliationRenderer.LineRenderer);
+ },
}, function () {
QUnit.module('Reconciliation');
diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml
index 1c67e6c4824..e81ca343db5 100644
--- a/addons/account/views/account_invoice_view.xml
+++ b/addons/account/views/account_invoice_view.xml
@@ -165,8 +165,8 @@
<field name="user_id"/>
<field name="date_due"/>
<field name="origin"/>
- <field name="amount_untaxed" string="Tax Excluded" sum="Total"/>
- <field name="amount_tax" sum="Total"/>
+ <field name="amount_untaxed_invoice_signed" string="Tax Excluded" sum="Total"/>
+ <field name="amount_tax_signed" string="Tax" sum="Total"/>
<field name="amount_total_signed" string="Total" sum="Total"/>
<field name="residual_signed" string="Amount Due" sum="Amount Due"/>
<field name="currency_id" invisible="1"/>
diff --git a/addons/account/views/account_portal_templates.xml b/addons/account/views/account_portal_templates.xml
index 858a7cf58df..30303fdb6ee 100644
--- a/addons/account/views/account_portal_templates.xml
+++ b/addons/account/views/account_portal_templates.xml
@@ -83,7 +83,7 @@
<div class="row mt16 o_portal_invoice_sidebar">
<!-- Sidebar -->
<t t-call="portal.portal_record_sidebar">
- <t t-set="classes" t-value="'col-12 col-lg flex-lg-grow-0 d-print-none'"/>
+ <t t-set="classes" t-value="'col-lg-auto d-print-none'"/>
<t t-set="title">
<h2 class="mb-0">
<b t-if="invoice.residual > 0" t-field="invoice.residual"/>
diff --git a/addons/account/views/report_payment_receipt_templates.xml b/addons/account/views/report_payment_receipt_templates.xml
index 871ae39479f..c0e11a2ef7f 100644
--- a/addons/account/views/report_payment_receipt_templates.xml
+++ b/addons/account/views/report_payment_receipt_templates.xml
@@ -46,7 +46,7 @@
<td><span t-field="inv.date_invoice"/></td>
<td><span t-field="inv.number"/></td>
<td class="text-right"><span t-field="inv.amount_total"/></td>
- <td class="text-right"><span t-esc="inv.amount_total - inv.residual" t-options="{'widget': 'monetary', 'display_currency': inv.currency_id}"/></td>
+ <td class="text-right"><span t-esc="o._get_invoice_payment_amount(inv)" t-options="{'widget': 'monetary', 'display_currency': inv.currency_id}"/></td>
<td class="text-right"><span t-field="inv.residual"/></td>
</tr>
</tbody>
diff --git a/addons/account_cancel/views/account_views.xml b/addons/account_cancel/views/account_views.xml
index e3dd3821294..18f308bd9c5 100644
--- a/addons/account_cancel/views/account_views.xml
+++ b/addons/account_cancel/views/account_views.xml
@@ -17,7 +17,7 @@
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_invoice_draft']" position="before">
- <button name="action_invoice_cancel" type="object" states="draft,open,paid" string="Cancel" groups="account.group_account_invoice"/>
+ <button name="action_invoice_cancel" type="object" states="draft,open,in_payment,paid" string="Cancel" groups="account.group_account_invoice"/>
</xpath>
</field>
</record>
@@ -28,7 +28,7 @@
<field name="inherit_id" ref="account.invoice_supplier_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_invoice_draft']" position="before">
- <button name="action_invoice_cancel" type="object" states="draft,open,paid" string="Cancel" groups="account.group_account_invoice"/>
+ <button name="action_invoice_cancel" type="object" states="draft,open,in_payment,paid" string="Cancel" groups="account.group_account_invoice"/>
</xpath>
</field>
</record>
diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py
index cffc8f00fd5..dbf6c2fec53 100644
--- a/addons/auth_signup/controllers/main.py
+++ b/addons/auth_signup/controllers/main.py
@@ -44,7 +44,7 @@ class AuthSignupHome(Home):
lang=user_sudo.lang,
auth_login=werkzeug.url_encode({'auth_login': user_sudo.email}),
).send_mail(user_sudo.id, force_send=True)
- return super(AuthSignupHome, self).web_login(*args, **kw)
+ return self.web_login(*args, **kw)
except UserError as e:
qcontext['error'] = e.name or e.value
except (SignupError, AssertionError) as e:
@@ -69,7 +69,7 @@ class AuthSignupHome(Home):
try:
if qcontext.get('token'):
self.do_signup(qcontext)
- return super(AuthSignupHome, self).web_login(*args, **kw)
+ return self.web_login(*args, **kw)
else:
login = qcontext.get('login')
assert login, _("No login provided.")
diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py
index 128594cc824..9d650a4b6fb 100644
--- a/addons/base_import_module/models/ir_module.py
+++ b/addons/base_import_module/models/ir_module.py
@@ -38,6 +38,8 @@ class IrModule(models.Model):
installed_mods = [m.name for m in known_mods if m.state == 'installed']
terp = load_information_from_description_file(module, mod_path=path)
+ if not terp:
+ return False
values = self.get_values_from_terp(terp)
if 'version' in terp:
values['latest_version'] = terp['version']
@@ -133,8 +135,8 @@ class IrModule(models.Model):
try:
# assert mod_name.startswith('theme_')
path = opj(module_dir, mod_name)
- self._import_module(mod_name, path, force=force)
- success.append(mod_name)
+ if self._import_module(mod_name, path, force=force):
+ success.append(mod_name)
except Exception as e:
_logger.exception('Error while importing module')
errors[mod_name] = exception_to_unicode(e)
diff --git a/addons/base_setup/data/base_setup_data.xml b/addons/base_setup/data/base_setup_data.xml
index 6499d00add4..e0d02590384 100644
--- a/addons/base_setup/data/base_setup_data.xml
+++ b/addons/base_setup/data/base_setup_data.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
- <record model="ir.config_parameter" id="show_effect">
+ <record model="ir.config_parameter" id="show_effect" forcecreate="False">
<field name="key">base_setup.show_effect</field>
<field name="value">True</field>
</record>
diff --git a/addons/board/static/src/js/add_to_board_menu.js b/addons/board/static/src/js/add_to_board_menu.js
index 1b3ebc3fc5b..57e98ca87d7 100644
--- a/addons/board/static/src/js/add_to_board_menu.js
+++ b/addons/board/static/src/js/add_to_board_menu.js
@@ -86,12 +86,18 @@ var AddToBoardMenu = Widget.extend({
var context = new Context(this.action.context);
context.add(searchQuery.context);
context.add({
- group_by: pyUtils.eval('groupbys', searchQuery.groupBys || [])
+ group_by: searchQuery.groupBy,
+ orderedBy: searchQuery.orderedBy,
});
this.trigger_up('get_controller_query_params', {
- callback: function (controllerContext) {
- context.add(controllerContext);
+ callback: function (controllerQueryParams) {
+ var queryContext = controllerQueryParams.context;
+ var allContext = _.extend(
+ _.omit(controllerQueryParams, ['context']),
+ queryContext
+ );
+ context.add(allContext);
}
});
diff --git a/addons/board/static/src/js/board_view.js b/addons/board/static/src/js/board_view.js
index 953f5d73101..ffdcfbeb7ed 100644
--- a/addons/board/static/src/js/board_view.js
+++ b/addons/board/static/src/js/board_view.js
@@ -272,7 +272,8 @@ var BoardRenderer = FormRenderer.extend({
searchQuery: {
context: context,
domain: domain,
- groupBy: context.group_by || [],
+ groupBy: typeof context.group_by === 'string' && context.group_by ? [context.group_by] : context.group_by || [],
+ orderedBy: context.orderedBy || [],
},
withControlPanel: false,
});
diff --git a/addons/board/static/tests/dashboard_tests.js b/addons/board/static/tests/dashboard_tests.js
index ebd8bcdcc5a..63252a75ac6 100644
--- a/addons/board/static/tests/dashboard_tests.js
+++ b/addons/board/static/tests/dashboard_tests.js
@@ -109,7 +109,7 @@ QUnit.test('display the no content helper', async function (assert) {
});
QUnit.test('basic functionality, with one sub action', async function (assert) {
- assert.expect(25);
+ assert.expect(26);
var form = await createView({
View: BoardView,
@@ -118,7 +118,7 @@ QUnit.test('basic functionality, with one sub action', async function (assert) {
arch: '<form string="My Dashboard">' +
'<board style="2-1">' +
'<column>' +
- '<action context="{}" view_mode="list" string="ABC" name="51" domain="[[\'foo\', \'!=\', \'False\']]"></action>' +
+ '<action context="{&quot;orderedBy&quot;: [{&quot;name&quot;: &quot;foo&quot;, &quot;asc&quot;: True}]}" view_mode="list" string="ABC" name="51" domain="[[\'foo\', \'!=\', \'False\']]"></action>' +
'</column>' +
'</board>' +
'</form>',
@@ -132,6 +132,12 @@ QUnit.test('basic functionality, with one sub action', async function (assert) {
}
if (route === '/web/dataset/search_read') {
assert.deepEqual(args.domain, [['foo', '!=', 'False']], "the domain should be passed");
+ assert.deepEqual(args.context.orderedBy, [{
+ 'name': 'foo',
+ 'asc': true,
+ }],
+ 'orderedBy is present in the search read when specified on the custom action'
+ );
}
if (route === '/web/view/edit_custom') {
assert.step('edit custom');
@@ -687,7 +693,19 @@ QUnit.test('dashboard intercepts custom events triggered by sub controllers', as
});
QUnit.test('save actions to dashboard', async function (assert) {
- assert.expect(3);
+ assert.expect(6);
+
+ testUtils.patch(ListController, {
+ getOwnedQueryParams: function () {
+ var result = this._super.apply(this, arguments);
+ result.context = {
+ 'fire': 'on the bayou',
+ }
+ return result;
+ }
+ });
+
+ this.data['partner'].fields.foo.sortable = true;
var actionManager = await createActionManager({
data: this.data,
@@ -697,6 +715,16 @@ QUnit.test('save actions to dashboard', async function (assert) {
},
mockRPC: function (route, args) {
if (route === '/board/add_to_dashboard') {
+ assert.deepEqual(args.context_to_save.group_by, ['foo'],
+ 'The group_by should have been saved');
+ assert.deepEqual(args.context_to_save.orderedBy,
+ [{
+ name: 'foo',
+ asc: true,
+ }],
+ 'The orderedBy should have been saved');
+ assert.strictEqual(args.context_to_save.fire, 'on the bayou',
+ 'The context of a controller should be passed and flattened');
assert.strictEqual(args.action_id, 1,
"should save the correct action");
assert.strictEqual(args.view_mode, 'list',
@@ -717,12 +745,22 @@ QUnit.test('save actions to dashboard', async function (assert) {
assert.containsOnce(actionManager, '.o_list_view',
"should display the list view");
+ // Sort the list
+ await testUtils.dom.click($('.o_column_sortable'));
+
+ // Group It
+ await testUtils.dom.click($('.o_search_options .o_dropdown button:contains(Group By)'));
+ await testUtils.dom.click($('.o_search_options .o_group_by_menu button'));
+ await testUtils.dom.click($('.o_search_options .o_group_by_menu button.o_apply_group'));
+
// add this action to dashboard
await testUtils.dom.click($('.o_search_options .o_dropdown button:contains(Favorites)'));
await testUtils.dom.click($('.o_add_to_board.o_menu_header'));
testUtils.fields.editInput($('input.o_add_to_board_input'), 'a name');
await testUtils.dom.click($('.o_add_to_board_confirm_button'));
+ testUtils.unpatch(ListController);
+
actionManager.destroy();
});
@@ -888,6 +926,44 @@ QUnit.test("Dashboard should use correct groupby", async function (assert) {
'</board>' +
'</form>',
mockRPC: function (route, args) {
+ if (args.method === 'read_group') {
+ assert.deepEqual(args.kwargs.groupby, ['bar'],
+ 'user defined groupby should have precedence on action groupby');
+ }
+ if (route === '/web/action/load') {
+ return Promise.resolve({
+ res_model: 'partner',
+ context: {
+ group_by: 'some_field',
+ },
+ views: [[4, 'list']],
+ });
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ 'partner,4,list':
+ '<list string="Partner"><field name="foo"/></list>',
+ },
+ });
+
+ form.destroy();
+});
+
+QUnit.test("Dashboard should use correct groupby when defined as a string of one field", function (assert) {
+ assert.expect(1);
+ var form = createView({
+ View: BoardView,
+ model: 'board',
+ data: this.data,
+ arch: '<form string="My Dashboard">' +
+ '<board style="2-1">' +
+ '<column>' +
+ '<action context="{\'group_by\': \'bar\'}" string="ABC" name="51"></action>' +
+ '</column>' +
+ '</board>' +
+ '</form>',
+ mockRPC: function (route, args) {
if (args.method === 'web_read_group') {
assert.deepEqual(args.kwargs.groupby, ['bar'],
'user defined groupby should have precedence on action groupby');
diff --git a/addons/calendar/data/mail_data.xml b/addons/calendar/data/mail_data.xml
index a4e2fa46907..bd44813d08f 100644
--- a/addons/calendar/data/mail_data.xml
+++ b/addons/calendar/data/mail_data.xml
@@ -8,6 +8,7 @@
<field name="email_from">${object.event_id.user_id.email or ''}</field>
<field name="email_to" >${('' if object.partner_id and object.partner_id.email and object.partner_id.email==object.email else object.email|safe)}</field>
<field name="partner_to">${object.partner_id and object.partner_id.email and object.partner_id.email==object.email and object.partner_id.id or False }</field>
+ <field name="lang">${object.partner_id.lang}</field>
<field name="body_html" type="html">
<div>
% set colors = {'needsAction': 'grey', 'accepted': 'green', 'tentative': '#FFFF00', 'declined': 'red'}
@@ -91,6 +92,7 @@
<field name="email_from">${object.event_id.user_id.email or ''}</field>
<field name="email_to" >${('' if object.partner_id and object.partner_id.email and object.partner_id.email==object.email else object.email|safe)}</field>
<field name="partner_to">${object.partner_id and object.partner_id.email and object.partner_id.email==object.email and object.partner_id.id or False }</field>
+ <field name="lang">${object.partner_id.lang}</field>
<field name="body_html" type="html">
<div>
% set colors = {'needsAction': 'grey', 'accepted': 'green', 'tentative': '#FFFF00', 'declined': 'red'}
@@ -174,6 +176,7 @@
<field name="email_from">${object.event_id.user_id.email or ''}</field>
<field name="email_to" >${('' if object.partner_id and object.partner_id.email and object.partner_id.email==object.email else object.email|safe)}</field>
<field name="partner_to">${object.partner_id and object.partner_id.email and object.partner_id.email==object.email and object.partner_id.id or False }</field>
+ <field name="lang">${object.partner_id.lang}</field>
<field name="body_html" type="html">
<div>
% set colors = {'needsAction': 'grey', 'accepted': 'green', 'tentative': '#FFFF00', 'declined': 'red'}
diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py
index 957bb1bdc61..2c0c2e32877 100644
--- a/addons/calendar/models/calendar.py
+++ b/addons/calendar/models/calendar.py
@@ -406,9 +406,8 @@ class AlarmManager(models.AbstractModel):
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']
- meeting = self.env['calendar.event'].browse(event_id)
+ for meeting in self.env['calendar.event'].search([('id', 'in', list(all_meetings))]): # cannot browse
+ max_delta = all_meetings[meeting.id]['max_duration']
if meeting.recurrency:
b_found = False
last_found = False
diff --git a/addons/calendar/models/mail_message.py b/addons/calendar/models/mail_message.py
index cd8ed5d89ae..143bea2d135 100644
--- a/addons/calendar/models/mail_message.py
+++ b/addons/calendar/models/mail_message.py
@@ -26,6 +26,7 @@ class Message(models.Model):
def _find_allowed_model_wise(self, doc_model, doc_dict):
if doc_model == 'calendar.event':
order = self._context.get('order', self.env[doc_model]._order)
- for virtual_id in self.env[doc_model].browse(doc_dict).get_recurrent_ids([], order=order):
+ records = self.env[doc_model].search([('id', 'in', list(doc_dict))])
+ for virtual_id in records.get_recurrent_ids([], order=order):
doc_dict.setdefault(virtual_id, doc_dict[get_real_ids(virtual_id)])
return super(Message, self)._find_allowed_model_wise(doc_model, doc_dict)
diff --git a/addons/crm/data/crm_data.xml b/addons/crm/data/crm_data.xml
index 006c982c03d..0ee888fea90 100644
--- a/addons/crm/data/crm_data.xml
+++ b/addons/crm/data/crm_data.xml
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
- <record id="sales_team.salesteam_website_sales" model="crm.team">
+ <record id="sales_team.salesteam_website_sales" model="crm.team" forcecreate="False">
<field name="use_opportunities" eval="False"/>
</record>
- <record id="sales_team.pos_sales_team" model="crm.team">
+ <record id="sales_team.pos_sales_team" model="crm.team" forcecreate="False">
<field name="use_opportunities" eval="False"/>
</record>
- <record id="sales_team.ebay_sales_team" model="crm.team">
+ <record id="sales_team.ebay_sales_team" model="crm.team" forcecreate="False">
<field name="use_opportunities" eval="False"/>
</record>
diff --git a/addons/crm/data/crm_demo.xml b/addons/crm/data/crm_demo.xml
index d57758c9106..de433c3afeb 100644
--- a/addons/crm/data/crm_demo.xml
+++ b/addons/crm/data/crm_demo.xml
@@ -5,7 +5,7 @@
<field name="groups_id" eval="[(4,ref('sales_team.group_sale_salesman'))]"/>
</record>
- <record id="sales_team.team_sales_department" model="crm.team">
+ <record id="sales_team.team_sales_department" model="crm.team" forcecreate="False">
<field name="alias_name">sales</field>
<field name="alias_user_id" ref="base.user_admin"/>
<field name="company_id" eval="False"/>
diff --git a/addons/delivery/models/sale_order.py b/addons/delivery/models/sale_order.py
index cd09c8490ab..0ded86cbea7 100644
--- a/addons/delivery/models/sale_order.py
+++ b/addons/delivery/models/sale_order.py
@@ -92,9 +92,15 @@ class SaleOrder(models.Model):
taxes_ids = self.fiscal_position_id.map_tax(taxes, carrier.product_id, self.partner_id).ids
# Create the sales order line
+ carrier_with_partner_lang = carrier.with_context(lang=self.partner_id.lang)
+ if carrier_with_partner_lang.product_id.description_sale:
+ so_description = '%s: %s' % (carrier_with_partner_lang.name,
+ carrier_with_partner_lang.product_id.description_sale)
+ else:
+ so_description = carrier_with_partner_lang.name
values = {
'order_id': self.id,
- 'name': carrier.with_context(lang=self.partner_id.lang).name,
+ 'name': so_description,
'product_uom_qty': 1,
'product_uom': carrier.product_id.uom_id.id,
'product_id': carrier.product_id.id,
diff --git a/addons/delivery/views/delivery_view.xml b/addons/delivery/views/delivery_view.xml
index 04da579f079..fc9fdb9a13a 100644
--- a/addons/delivery/views/delivery_view.xml
+++ b/addons/delivery/views/delivery_view.xml
@@ -94,7 +94,7 @@
<label for="free_over"/>
</div>
<field nolabel="1" name="amount" attrs="{'required':[('free_over','!=', False)], 'invisible':[('free_over','=', False)]}"/>
- <field name="product_id" context="{'default_type': 'service', 'sale_ok': False, 'purchase_ok': False}" />
+ <field name="product_id" context="{'default_type': 'service', 'default_sale_ok': False, 'default_purchase_ok': False}" />
<field name="company_id" groups="base.group_multi_company"/>
<field name="can_generate_return" invisible="1"/>
<field name="return_label_on_delivery" attrs="{'invisible': [('can_generate_return', '=', False)]}"/>
diff --git a/addons/delivery/wizard/choose_delivery_carrier.py b/addons/delivery/wizard/choose_delivery_carrier.py
index 454190a101e..c7bac396c61 100644
--- a/addons/delivery/wizard/choose_delivery_carrier.py
+++ b/addons/delivery/wizard/choose_delivery_carrier.py
@@ -53,7 +53,7 @@ class ChooseDeliveryCarrier(models.TransientModel):
def _compute_available_carrier(self):
carriers = self.env['delivery.carrier'].search([])
for rec in self:
- rec.available_carrier_ids = carriers.available_carriers(rec.partner_id) if rec.partner_id else carriers
+ rec.available_carrier_ids = carriers.available_carriers(rec.order_id.partner_shipping_id) if rec.partner_id else carriers
def update_price(self):
vals = self.carrier_id.rate_shipment(self.order_id)
diff --git a/addons/event/controllers/main.py b/addons/event/controllers/main.py
index 481fc5f27ba..f844e6a5d51 100644
--- a/addons/event/controllers/main.py
+++ b/addons/event/controllers/main.py
@@ -9,7 +9,7 @@ from odoo.http import Controller, request, route, content_disposition
class EventController(Controller):
@route(['''/event/<model("event.event", "[('state', 'in', ('confirm', 'done'))]"):event>/ics'''], type='http', auth="public")
- def event_ics_file(self, event):
+ def event_ics_file(self, event, **kwargs):
if not event or not event.registration_ids:
return request.not_found()
files = event._get_ics_file()
diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py
index fac1730bead..52017208adc 100644
--- a/addons/event_sale/models/sale_order.py
+++ b/addons/event_sale/models/sale_order.py
@@ -42,11 +42,11 @@ class SaleOrderLine(models.Model):
registrations linked to this line. This method update existing registrations
and create new one for missing one. """
Registration = self.env['event.registration'].sudo()
- registrations = Registration.search([('sale_order_line_id', 'in', self.ids), ('state', '!=', 'cancel')])
+ registrations = Registration.search([('sale_order_line_id', 'in', self.ids)])
for so_line in self.filtered('event_id'):
existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.id)
if confirm:
- existing_registrations.filtered(lambda self: self.state != 'open').confirm_registration()
+ existing_registrations.filtered(lambda self: self.state not in ['open', 'cancel']).confirm_registration()
if cancel_to_draft:
existing_registrations.filtered(lambda self: self.state == 'cancel').do_draft()
diff --git a/addons/hr/views/hr_views.xml b/addons/hr/views/hr_views.xml
index ff8df2f3992..c60b8613788 100644
--- a/addons/hr/views/hr_views.xml
+++ b/addons/hr/views/hr_views.xml
@@ -304,6 +304,13 @@
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction', '=', True)]"/>
+ <separator/>
+ <filter string="Late Activities" name="activities_overdue"
+ domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"/>
+ <filter string="Today Activities" name="activities_today"
+ domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
+ <filter string="Future Activities" name="activities_upcoming_all"
+ domain="[('activity_ids.date_deadline', '&gt;', context_today().strftime('%Y-%m-%d'))]"/>
<group expand="0" string="Group By">
<filter name="group_manager" string="Manager" domain="[]" context="{'group_by':'parent_id'}"/>
<filter name="group_coach" string="Coach" domain="[]" context="{'group_by':'coach_id'}"/>
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index b0a8c584c67..0e01c61851d 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -369,7 +369,7 @@ class HrExpense(models.Model):
# get move line values
move_line_values = move_line_values_by_expense.get(expense.id)
move_line_dst = move_line_values[-1]
- total_amount = abs(move_line_dst['debit'])
+ total_amount = move_line_dst['debit'] or -move_line_dst['credit']
total_amount_currency = move_line_dst['amount_currency']
# create one more move line, a counterline for the total on payable account
diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py
index 34afa16eb5d..3ae7d554205 100644
--- a/addons/hr_holidays/models/hr_leave.py
+++ b/addons/hr_holidays/models/hr_leave.py
@@ -335,19 +335,22 @@ class HolidaysRequest(models.Model):
@api.onchange('holiday_type')
def _onchange_type(self):
- if self.holiday_type == 'employee' and not self.employee_id:
- self.employee_id = self.env.user.employee_ids[:1].id
+ if self.holiday_type == 'employee':
+ if not self.employee_id:
+ self.employee_id = self.env.user.employee_ids[:1].id
self.mode_company_id = False
self.category_id = False
- elif self.holiday_type == 'company' and not self.mode_company_id:
+ elif self.holiday_type == 'company':
self.employee_id = False
- self.mode_company_id = self.env.user.company_id.id
+ if not self.mode_company_id:
+ self.mode_company_id = self.env.user.company_id.id
self.category_id = False
- elif self.holiday_type == 'department' and not self.department_id:
+ elif self.holiday_type == 'department':
self.employee_id = False
self.mode_company_id = False
- self.department_id = self.env.user.employee_ids[:1].department_id.id
self.category_id = False
+ if not self.department_id:
+ self.department_id = self.env.user.employee_ids[:1].department_id.id
elif self.holiday_type == 'category':
self.employee_id = False
self.mode_company_id = False
diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py
index abfb441a0c9..d505192dfc8 100644
--- a/addons/hr_holidays/models/hr_leave_allocation.py
+++ b/addons/hr_holidays/models/hr_leave_allocation.py
@@ -244,16 +244,26 @@ class HolidaysAllocation(models.Model):
@api.onchange('holiday_type')
def _onchange_type(self):
- if self.holiday_type == 'employee' and not self.employee_id:
- if self.env.user.employee_ids:
- self.employee_id = self.env.user.employee_ids[0]
+ if self.holiday_type == 'employee':
+ if not self.employee_id:
+ self.employee_id = self.env.user.employee_ids[:1].id
+ self.mode_company_id = False
+ self.category_id = False
+ elif self.holiday_type == 'company':
+ self.employee_id = False
+ if not self.mode_company_id:
+ self.mode_company_id = self.env.user.company_id.id
+ self.category_id = False
elif self.holiday_type == 'department':
- if self.env.user.employee_ids:
- self.department_id = self.department_id or self.env.user.employee_ids[0].department_id
- self.employee_id = None
- else:
- self.employee_id = None
- self.department_id = None
+ self.employee_id = False
+ self.mode_company_id = False
+ self.category_id = False
+ if not self.department_id:
+ self.department_id = self.env.user.employee_ids[:1].department_id.id
+ elif self.holiday_type == 'category':
+ self.employee_id = False
+ self.mode_company_id = False
+ self.department_id = False
@api.onchange('employee_id')
def _onchange_employee(self):
diff --git a/addons/hr_holidays/tests/test_leave_requests.py b/addons/hr_holidays/tests/test_leave_requests.py
index 67d468b0a44..9f32d96e6c6 100644
--- a/addons/hr_holidays/tests/test_leave_requests.py
+++ b/addons/hr_holidays/tests/test_leave_requests.py
@@ -6,6 +6,7 @@ from dateutil.relativedelta import relativedelta
from odoo import fields
from odoo.exceptions import ValidationError
from odoo.tools import mute_logger
+from odoo.tests.common import Form
from odoo.addons.hr_holidays.tests.common import TestHrHolidaysBase
@@ -150,3 +151,25 @@ class TestLeaveRequests(TestHrHolidaysBase):
'date_to': fields.Datetime.from_string('2017-07-11 19:00:00'),
'number_of_days': 1,
})
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_department_leave(self):
+ """ Create a department leave """
+ 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))
+ leave_form.holiday_status_id = self.holidays_type_1
+ leave_form.holiday_type = 'department'
+ leave_form.department_id = self.hr_dept
+ leave = leave_form.save()
+ leave.action_approve()
+ member_ids = self.hr_dept.member_ids.ids
+ self.assertEqual(self.env['hr.leave'].search_count([('employee_id', 'in', member_ids)]), len(member_ids), "Leave should be created for members of department")
+
+ @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
+ def test_allocation_request(self):
+ """ Create an allocation request """
+ # employee should be set to current user
+ allocation_form = Form(self.env['hr.leave.allocation'].sudo(self.user_employee))
+ allocation_form.holiday_status_id = self.holidays_type_1
+ allocation = allocation_form.save()
diff --git a/addons/hr_presence/views/hr_employee_views.xml b/addons/hr_presence/views/hr_employee_views.xml
index 9ea9cbf0019..932bf74eb37 100644
--- a/addons/hr_presence/views/hr_employee_views.xml
+++ b/addons/hr_presence/views/hr_employee_views.xml
@@ -64,7 +64,7 @@
</field>
</record>
- <record id="hr_employee_view_kanban" model="ir.ui.view">
+ <record id="hr_employee_view_kanban_inherit" model="ir.ui.view">
<field name="name">hr.employee</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.hr_kanban_view_employees"/>
diff --git a/addons/hw_screen/static/src/js/worker.js b/addons/hw_screen/static/src/js/worker.js
index 4031e85cf52..5949ab39cef 100644
--- a/addons/hw_screen/static/src/js/worker.js
+++ b/addons/hw_screen/static/src/js/worker.js
@@ -9,7 +9,7 @@
function longpolling() {
$.ajax({
type: 'POST',
- url: 'http://'+window.location.host+'/point_of_sale/get_serialized_order',
+ url: window.location.origin+'/point_of_sale/get_serialized_order',
dataType: 'json',
beforeSend: function(xhr){xhr.setRequestHeader('Content-Type', 'application/json');},
data: JSON.stringify({jsonrpc: '2.0'}),
@@ -47,11 +47,14 @@
foreign_js();
}
}
+ if (!stop_longpolling) {
+ longpolling();
+ }
},
- complete: function(jqXHR,err) {
+ error: function (jqXHR, status, err) {
if (!stop_longpolling) {
- longpolling();
+ setTimeout(longpolling, 5000);
}
},
diff --git a/addons/l10n_ca/data/account_tax_data.xml b/addons/l10n_ca/data/account_tax_data.xml
index 803e63c0435..174c30ef360 100644
--- a/addons/l10n_ca/data/account_tax_data.xml
+++ b/addons/l10n_ca/data/account_tax_data.xml
@@ -112,7 +112,7 @@
<field name="type_tax_use">sale</field>
<field name="amount">100</field>
<field name="amount_type">group</field>
- <field name="children_tax_ids" eval="[(6,0,[ref('pst_bc_sale_en')])]"/>
+ <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/models/mail_template.py b/addons/l10n_ch/models/mail_template.py
index e3a1deca1dd..7ec29d4d508 100644
--- a/addons/l10n_ch/models/mail_template.py
+++ b/addons/l10n_ch/models/mail_template.py
@@ -36,6 +36,9 @@ class MailTemplate(models.Model):
pdf = self.env.ref('l10n_ch.l10n_ch_isr_report').render_qweb_pdf([res_id])[0]
pdf = base64.b64encode(pdf)
- attachments_list = multi_mode and rslt[res_id]['attachments'] or rslt['attachments']
- attachments_list.append((report_name, pdf))
+ attachments_list = multi_mode and rslt[res_id].get('attachments', False) or rslt.get('attachments', False)
+ if attachments_list:
+ attachments_list.append((report_name, pdf))
+ else:
+ rslt[res_id]['attachments'] = [(report_name, pdf)]
return rslt
diff --git a/addons/l10n_de_skr03/data/account.account.template.csv b/addons/l10n_de_skr03/data/account.account.template.csv
index 092915add9a..4ef26cb8bb5 100644
--- a/addons/l10n_de_skr03/data/account.account.template.csv
+++ b/addons/l10n_de_skr03/data/account.account.template.csv
@@ -510,7 +510,7 @@
"account_1784","1784","USt EG-Erwerb Neufahrzeuge ohne UStID","l10n_de.tag_de_asset_bs_B_II_4","account.data_account_type_current_liabilities","l10n_de_skr03.l10n_de_chart_template","False"
"account_1785","1785","Umsatzsteuer nach § 13b UStG","l10n_de.tag_de_asset_bs_B_II_4","account.data_account_type_current_liabilities","l10n_de_skr03.l10n_de_chart_template","False"
"account_1787","1787","Umsatzsteuer nach § 13b UStG 19%","l10n_de.tag_de_asset_bs_B_II_4","account.data_account_type_current_liabilities","l10n_de_skr03.l10n_de_chart_template","False"
-"account_1788","1788","Aufgeschobene Einfuhr-Umsatzsteuer","l10n_de.tag_de_liabilities_bs_D_8","account.data_account_type_current_liabilities","l10n_de_skr03.l10n_de_chart_template","False"
+"account_1788","1788","Aufgeschobene Einfuhr-Umsatzsteuer","l10n_de.tag_de_liabilities_bs_D_8","account.data_account_type_current_liabilities","l10n_de_skr03.l10n_de_chart_template","True"
"account_1789","1789","Umsatzsteuer laufendes Jahr","l10n_de.tag_de_liabilities_bs_D_8","account.data_account_type_current_liabilities","l10n_de_skr03.l10n_de_chart_template","False"
"account_1790","1790","Umsatzsteuer Vorjahr","l10n_de.tag_de_liabilities_bs_D_8","account.data_account_type_current_liabilities","l10n_de_skr03.l10n_de_chart_template","False"
"account_1791","1791","Umsatzsteuer frühere Jahre","l10n_de.tag_de_liabilities_bs_D_8","account.data_account_type_current_liabilities","l10n_de_skr03.l10n_de_chart_template","False"
@@ -845,9 +845,10 @@
"account_3505","3505","Wareneingang 5,5% Vorsteuer","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr03.l10n_de_chart_template","False"
"account_3540","3540","Wareneingang 10,7% Vorsteuer","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr03.l10n_de_chart_template","False"
"account_3550","3550","Steuerfreier EG-Erwerb","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr03.l10n_de_chart_template","False"
-"account_3551","3551","Wareneingang, im Drittland steuerbar","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr03.l10n_de_chart_template","False"
+"account_3551","3551","Wareneingang, im Drittland steuerbar (19%)","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr03.l10n_de_chart_template","False"
"account_3552","3552","Erwerb 1. Abnehmer im Dreiecksgeschäft","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr03.l10n_de_chart_template","False"
"account_3553","3553","Erwerb Waren als letzter Abnehmer innerhalb Dreiecksgeschäft 19% Vorsteuer und 19% Umsatzsteuer","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr03.l10n_de_chart_template","False"
+"account_3557","3557","Wareneingang, im Drittland steuerbar (7%)","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr03.l10n_de_chart_template","False"
"account_3558","3558","Wareneingang, im anderen EG-Land stb.","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr03.l10n_de_chart_template","False"
"account_3559","3559","Steuerfreie Einfuhren","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr03.l10n_de_chart_template","False"
"account_3560","3560","Waren aus USt-Lager 7% Vorsteuer, 7% USt","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr03.l10n_de_chart_template","False"
@@ -1214,7 +1215,7 @@
"account_8730","8730","Gewährte Skonti","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr03.l10n_de_chart_template","False"
"account_8731","8731","Gewährte Skonti 7% USt","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr03.l10n_de_chart_template","False"
"account_8736","8736","Gewährte Skonti 19% USt","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr03.l10n_de_chart_template","False"
-"account_8738","8738","Gewährte Skonti aus Lieferungen von Mobilfunkgeräten/Schaltkreisen, für die der Leistungsempfänger die Umsatzsteuer nach § 13b UStG schuldet","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr03.l10n_de_chart_template","False"
+"account_8738","8738","Gewährte Skonti aus Lieferungen von Mobilfunkgeräten/Schaltkreisen, für die der Leistungsempfänger die Umsatzsteuer nach § 13b Abs. 2 Nr. 10 UStG schuldet","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr03.l10n_de_chart_template","False"
"account_8741","8741","Gewährte Skonti Leistungen §13b UStG","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr03.l10n_de_chart_template","False"
"account_8742","8742","Gewährte Skonti aus Erlöse aus im anderen EU-Land steuerpflichtigen sonstigen Leistungen, für die der Leistungsempfänger die Umsatzsteuer schuldet","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr03.l10n_de_chart_template","False"
"account_8743","8743","Gewährte Skonti stfr. EG-Lieferung","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr03.l10n_de_chart_template","False"
@@ -1280,4 +1281,5 @@
"account_9001","9001","Saldenvorträge","","account.data_account_type_other_income","l10n_de_skr03.l10n_de_chart_template","False"
"account_9008","9008","Saldenvorträge Debitoren","","account.data_account_type_other_income","l10n_de_skr03.l10n_de_chart_template","False"
"account_9009","9009","Saldenvorträge Kreditoren","","account.data_account_type_other_income","l10n_de_skr03.l10n_de_chart_template","False"
-"account_9090","9090","Summenvortragskonto","","account.data_account_type_other_income","l10n_de_skr03.l10n_de_chart_template","False" \ No newline at end of file
+"account_9089","9089","Offene Posten aus 2019","","account.data_account_type_other_income","l10n_de_skr03.l10n_de_chart_template","False"
+"account_9090","9090","Summenvortragskonto","","account.data_account_type_other_income","l10n_de_skr03.l10n_de_chart_template","False"
diff --git a/addons/l10n_de_skr03/data/account_tax_fiscal_position_data.xml b/addons/l10n_de_skr03/data/account_tax_fiscal_position_data.xml
index 41d065d79c9..4d172b2999c 100644
--- a/addons/l10n_de_skr03/data/account_tax_fiscal_position_data.xml
+++ b/addons/l10n_de_skr03/data/account_tax_fiscal_position_data.xml
@@ -77,7 +77,74 @@
<field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_93')])]"/>
<field name="tax_group_id" ref="tax_group_0"/>
</record>
-
+ <record id="tax_eu_19_purchase_ust_no_vst_skr03" model="account.tax.template">
+ <field name="sequence">97</field>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ <field name="name">19% Umsatzsteuer aus innergem. Erwerb (kein Vorsteuerabzug)</field>
+ <field name="description">19% USt innerg. Erw. - 0% Vorsteuer</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">-19</field>
+ <field name="refund_account_id" ref="account_1779"/>
+ <field name="account_id" ref="account_1779"/>
+ <field name="type_tax_use">none</field>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_89')])]"/>
+ <field name="tax_group_id" ref="tax_group_19"/>
+ </record>
+ <record id="tax_eu_19_purchase_vst_no_vst_skr03" model="account.tax.template">
+ <field name="sequence">97</field>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ <field name="name">19% Vorsteuer aus innergem. Erwerb (kein Vorsteuerabzug)</field>
+ <field name="description">19% VSt innerg. Erw. - 0% Vorsteuer</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">19</field>
+ <field name="type_tax_use">none</field>
+ <field name="tax_group_id" ref="tax_group_19"/>
+ </record>
+ <record id="tax_eu_19_purchase_no_vst_skr03" model="account.tax.template">
+ <field name="sequence">20</field>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ <field name="name">Innergem. Erwerb 19%USt/0%VSt</field>
+ <field name="description">innergem. Erwerb 19% - 0% Vorsteuer</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="children_tax_ids" eval="[(6,0,[ref('tax_eu_19_purchase_ust_no_vst_skr03'), ref('tax_eu_19_purchase_vst_no_vst_skr03')])]"/>
+ <field name="tax_group_id" ref="tax_group_19"/>
+ </record>
+ <record id="tax_eu_7_purchase_ust_no_vst_skr03" model="account.tax.template">
+ <field name="sequence">97</field>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ <field name="name">7% Umsatzsteuer aus innergem. Erwerb (kein Vorsteuerabzug)</field>
+ <field name="description">7% USt innerg. Erw. - 0% Vorsteuer</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">-7</field>
+ <field name="refund_account_id" ref="account_1779"/>
+ <field name="account_id" ref="account_1779"/>
+ <field name="type_tax_use">none</field>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_93')])]"/>
+ <field name="tax_group_id" ref="tax_group_7"/>
+ </record>
+ <record id="tax_eu_7_purchase_vst_no_vst_skr03" model="account.tax.template">
+ <field name="sequence">97</field>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ <field name="name">7% Vorsteuer aus innergem. Erwerb (kein Vorsteuerabzug)</field>
+ <field name="description">7% VSt innerg. Erw. - 0% Vorsteuer</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">7</field>
+ <field name="type_tax_use">none</field>
+ <field name="tax_group_id" ref="tax_group_7"/>
+ </record>
+ <record id="tax_eu_7_purchase_no_vst_skr03" model="account.tax.template">
+ <field name="sequence">20</field>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ <field name="name">Innergem. Erwerb 7%USt/0%VSt</field>
+ <field name="description">innergem. Erwerb 7% - 0% Vorsteuer</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="children_tax_ids" eval="[(6,0,[ref('tax_eu_7_purchase_ust_no_vst_skr03'), ref('tax_eu_7_purchase_vst_no_vst_skr03')])]"/>
+ <field name="tax_group_id" ref="tax_group_7"/>
+ </record>
<record id="tax_eu_car_purchase_ust_skr03" model="account.tax.template">
<field name="sequence">99</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
@@ -163,29 +230,75 @@
<record id="tax_import_19_skr03" model="account.tax.template">
<field name="sequence">20</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
- <field name="name">19% Einfuhrumsatzsteuer</field>
+ <field name="name">19% Einfuhrumsatzsteuer (abzugsf. Vorsteuer)</field>
<field name="description">19% EUSt</field>
<field name="amount_type">percent</field>
<field name="amount">19</field>
<field name="refund_account_id" ref="account_1588"/>
<field name="account_id" ref="account_1588"/>
- <field name="type_tax_use">purchase</field>
+ <field name="type_tax_use">none</field>
<field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_62')])]"/>
<field name="tax_group_id" ref="tax_group_19"/>
</record>
- <record id="tax_import_7_skr03" model="account.tax.template">
+ <record id="tax_import_19_payable_skr03" model="account.tax.template">
+ <field name="sequence">98</field>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ <field name="name">19% Einfuhrumsatzsteuer (zu bezahlender Aufschub)</field>
+ <field name="description">19% EUSt (zu bezahlender Aufschub)</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">-19</field>
+ <field name="refund_account_id" ref="account_1788"/>
+ <field name="account_id" ref="account_1788"/>
+ <field name="type_tax_use">none</field>
+ <field name="tax_group_id" ref="tax_group_19"/>
+ </record>
+ <record id="tax_import_19_and_payable_skr03" model="account.tax.template">
<field name="sequence">21</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
- <field name="name">7% Einfuhrumsatzsteuer</field>
- <field name="description">7% EUSt</field>
+ <field name="name">19% Einfuhrumsatzsteuer (§21 Abs.3 UstG)</field>
+ <field name="description">Einfuhrumsatzsteuer 19%</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="children_tax_ids" eval="[(6,0,[ref('tax_import_19_skr03'), ref('tax_import_19_payable_skr03')])]"/>
+ <field name="tax_group_id" ref="tax_group_19"/>
+ </record>
+ <record id="tax_import_7_skr03" model="account.tax.template">
+ <field name="sequence">98</field>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ <field name="name">7% Einfuhrumsatzsteuer (abzugsf. Vorsteuer)</field>
+ <field name="description">7% EUSt (abzugsf. Vorsteuer)</field>
<field name="amount_type">percent</field>
<field name="amount">7</field>
<field name="refund_account_id" ref="account_1588"/>
<field name="account_id" ref="account_1588"/>
- <field name="type_tax_use">purchase</field>
+ <field name="type_tax_use">none</field>
<field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_62')])]"/>
<field name="tax_group_id" ref="tax_group_7"/>
</record>
+ <record id="tax_import_7_payable_skr03" model="account.tax.template">
+ <field name="sequence">98</field>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ <field name="name">7% Einfuhrumsatzsteuer (zu bezahlender Aufschub)</field>
+ <field name="description">7% EUSt (zu bezahlender Aufschub)</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">-7</field>
+ <field name="refund_account_id" ref="account_1788"/>
+ <field name="account_id" ref="account_1788"/>
+ <field name="type_tax_use">none</field>
+ <field name="tax_group_id" ref="tax_group_7"/>
+ </record>
+ <record id="tax_import_7_and_payable_skr03" model="account.tax.template">
+ <field name="sequence">21</field>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ <field name="name">7% Einfuhrumsatzsteuer (§21 Abs.3 UstG)</field>
+ <field name="description">Einfuhrumsatzsteuer 7%</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="children_tax_ids" eval="[(6,0,[ref('tax_import_7_skr03'), ref('tax_import_7_payable_skr03')])]"/>
+ <field name="tax_group_id" ref="tax_group_7"/>
+ </record>
<record id="tax_eu_purchase_tax_free_skr03" model="account.tax.template">
<field name="sequence">22</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
@@ -816,7 +929,6 @@
<field name="children_tax_ids" eval="[(6,0,[ref('tax_ust_19_purchase_3eck_last_skr03'), ref('tax_vst_19_purchase_3eck_last_skr03')])]"/>
<field name="tax_group_id" ref="tax_group_0"/>
</record>
-
<record id="tax_ust_19_purchase_13b_werk_ausland_skr03" model="account.tax.template">
<field name="sequence">105</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
@@ -969,23 +1081,38 @@
<field name="children_tax_ids" eval="[(6,0,[ref('tax_ust_7_purchase_13a_auslagerung_skr03'), ref('tax_vst_7_purchase_13a_auslagerung_skr03')])]"/>
<field name="tax_group_id" ref="tax_group_0"/>
</record>
+ <record id="fiscal_position_domestic_skr03" model="account.fiscal.position.template">
+ <field name="sequence">1</field>
+ <field name="name">Geschäftspartner Inland</field>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ <field name="auto_apply" eval="True" />
+ <field name="country_id" ref="base.de"></field>
+ </record>
<record id="fiscal_position_non_eu_partner_service_skr03" model="account.fiscal.position.template">
+ <field name="sequence">6</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
- <field name="name">Dienstleister Ausland</field>
+ <field name="name">Dienstleister Ausland (Nicht-EU)</field>
</record>
<record id="fiscal_position_non_eu_partner_skr03" model="account.fiscal.position.template">
+ <field name="sequence">5</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
- <field name="name">Geschäftspartner Ausland</field>
+ <field name="name">Geschäftspartner Ausland (Nicht-EU)</field>
</record>
<record id="fiscal_position_eu_vat_id_partner_skr03" model="account.fiscal.position.template">
+ <field name="sequence">2</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
<field name="name">Geschäftspartner EU (mit USt-ID)</field>
+ <field name="auto_apply" eval="True" />
+ <field name="country_group_id" ref="base.europe"></field>
</record>
<record id="fiscal_position_eu_vat_id_partner_service_skr03" model="account.fiscal.position.template">
+ <field name="sequence">3</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
<field name="name">Dienstleister EU (mit USt-ID)</field>
+ <field name="vat_required" eval="True" />
</record>
<record id="fiscal_position_eu_no_id_partner_skr03" model="account.fiscal.position.template">
+ <field name="sequence">4</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
<field name="name">Geschäftspartner EU (ohne USt-ID)</field>
</record>
@@ -1021,12 +1148,12 @@
</record>
<record id="account_fiscal_position_tax_eu_no_id_purchase_19_skr03" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_eu_no_id_partner_skr03"/>
- <field name="tax_dest_id" ref="tax_import_19_skr03"/>
+ <field name="tax_dest_id" ref="tax_eu_19_purchase_no_vst_skr03"/>
<field name="tax_src_id" ref="tax_vst_19_skr03"/>
</record>
<record id="account_fiscal_position_tax_eu_no_id_purchase_7_skr03" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_eu_no_id_partner_skr03"/>
- <field name="tax_dest_id" ref="tax_import_7_skr03"/>
+ <field name="tax_dest_id" ref="tax_eu_7_purchase_no_vst_skr03"/>
<field name="tax_src_id" ref="tax_vst_7_skr03"/>
</record>
<record id="account_fiscal_position_tax_eu_no_id_sale_19_skr03" model="account.fiscal.position.tax.template">
@@ -1056,7 +1183,7 @@
</record>
<record id="account_fiscal_position_tax_non_eu_purchase_19_skr03" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_non_eu_partner_skr03"/>
- <field name="tax_dest_id" ref="tax_import_19_skr03"/>
+ <field name="tax_dest_id" ref="tax_import_19_and_payable_skr03"/>
<field name="tax_src_id" ref="tax_vst_19_skr03"/>
</record>
<record id="account_fiscal_position_tax_non_eu_purchase_services_19_skr03" model="account.fiscal.position.tax.template">
@@ -1066,7 +1193,7 @@
</record>
<record id="account_fiscal_position_tax_non_eu_purchase_7_skr03" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_non_eu_partner_skr03"/>
- <field name="tax_dest_id" ref="tax_import_7_skr03"/>
+ <field name="tax_dest_id" ref="tax_import_7_and_payable_skr03"/>
<field name="tax_src_id" ref="tax_vst_7_skr03"/>
</record>
@@ -1138,7 +1265,7 @@
<record id="account_fiscal_position_account_non_eu_purchase_19_skr03" model="account.fiscal.position.account.template">
<field name="position_id" ref="fiscal_position_non_eu_partner_skr03"/>
<field name="account_src_id" ref="account_3400" />
- <field name="account_dest_id" ref="account_3435" />
+ <field name="account_dest_id" ref="account_3551" />
</record>
<record id="account_fiscal_position_acc_non_eu_purchase_services_19_skr03" model="account.fiscal.position.account.template">
<field name="position_id" ref="fiscal_position_non_eu_partner_service_skr03"/>
@@ -1148,7 +1275,7 @@
<record id="account_fiscal_position_account_non_eu_purchase_7_skr03" model="account.fiscal.position.account.template">
<field name="position_id" ref="fiscal_position_non_eu_partner_skr03"/>
<field name="account_src_id" ref="account_3300" />
- <field name="account_dest_id" ref="account_3430" />
+ <field name="account_dest_id" ref="account_3557" />
</record>
<record id="account_fiscal_position_account_eu_no_id_sale_19_skr03" model="account.fiscal.position.account.template">
<field name="position_id" ref="fiscal_position_eu_no_id_partner_skr03"/>
@@ -1239,11 +1366,11 @@
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_3066" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_eu_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_7_purchase_no_vst_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_3067" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_eu_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_no_vst_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_3070" model="account.account.template">
@@ -1359,11 +1486,11 @@
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_3430" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_7_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_7_purchase_no_vst_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_3435" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_no_vst_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_3440" model="account.account.template">
@@ -1379,17 +1506,21 @@
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_3550" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_purchase_tax_free_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_3551" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_import_19_and_payable_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_3553" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_ust_19_purchase_3eck_last_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
+ <record id="account_3557" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_import_7_and_payable_skr03')])]"/>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ </record>
<record id="account_3560" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_ust_7_purchase_13a_auslagerung_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
@@ -1546,12 +1677,16 @@
<field name="tax_ids" eval="[(6, 0, [ref('tax_eu_sale_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
+ <record id="account_8130" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_3eck_first_skr03')])]"/>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ </record>
<record id="account_8135" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_free_newcar_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8140" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr03_ohne_vst')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr03_mit_vst')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8150" model="account.account.template">
@@ -1567,7 +1702,7 @@
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8194" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr03_mit_vst')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8195" model="account.account.template">
@@ -1583,23 +1718,27 @@
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8310" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_7_purchase_ust_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_eu_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8315" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_ust_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_eu_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8331" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_ust_goods_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr03')])]"/>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ </record>
+ <record id="account_8335" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_free_mobil_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8336" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_not_taxable_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8337" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_ust_goods_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_free_bau_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8338" model="account.account.template">
@@ -1619,11 +1758,11 @@
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8514" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr03_ohne_vst')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8515" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr03_mit_vst')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8516" model="account.account.template">
@@ -1635,11 +1774,11 @@
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8574" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr03_ohne_vst')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8575" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr03_mit_vst')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8576" model="account.account.template">
@@ -1658,6 +1797,10 @@
<field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
+ <record id="account_8609" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr03_ohne_vst')])]"/>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ </record>
<record id="account_8611" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
@@ -1674,6 +1817,22 @@
<field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
+ <record id="account_8701" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr03_ohne_vst')])]"/>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ </record>
+ <record id="account_8702" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr03_mit_vst')])]"/>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ </record>
+ <record id="account_8703" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr03_ohne_vst')])]"/>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ </record>
+ <record id="account_8704" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr03_mit_vst')])]"/>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ </record>
<record id="account_8705" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_export_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
@@ -1691,11 +1850,11 @@
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8725" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_7_purchase_ust_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_eu_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8726" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_ust_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_eu_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8731" model="account.account.template">
@@ -1706,8 +1865,12 @@
<field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
+ <record id="account_8738" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_vst_ust_19_purchase_13b_mobil_skr03')])]"/>
+ <field name="chart_template_id" ref="l10n_de_chart_template"/>
+ </record>
<record id="account_8741" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_ust_goods_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_free_mobil_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8742" model="account.account.template">
@@ -1715,11 +1878,11 @@
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8746" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_7_purchase_ust_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_eu_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8748" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_ust_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_eu_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8750" model="account.account.template">
@@ -1747,7 +1910,7 @@
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8808" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_sale_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8819" model="account.account.template">
@@ -1759,11 +1922,11 @@
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8827" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_third_country_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_export_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8828" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_sale_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8850" model="account.account.template">
@@ -1799,7 +1962,7 @@
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8919" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr03')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_not_taxable_skr03')])]"/>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="account_8920" model="account.account.template">
diff --git a/addons/l10n_de_skr04/data/account.account.template.csv b/addons/l10n_de_skr04/data/account.account.template.csv
index 77734af2205..e105eb7e831 100644
--- a/addons/l10n_de_skr04/data/account.account.template.csv
+++ b/addons/l10n_de_skr04/data/account.account.template.csv
@@ -548,7 +548,7 @@
"chart_skr04_3840","3840","USt laufendes Jahr","l10n_de.tag_de_liabilities_bs_D_8","account.data_account_type_current_liabilities","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_3841","3841","USt Vorjahr","l10n_de.tag_de_liabilities_bs_D_8","account.data_account_type_non_current_liabilities","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_3845","3845","USt frühere Jahre","l10n_de.tag_de_liabilities_bs_D_8","account.data_account_type_non_current_liabilities","l10n_de_skr04.l10n_chart_de_skr04",""
-"chart_skr04_3850","3850","Aufgeschobene Einfuhrumsatzsteuer","l10n_de.tag_de_liabilities_bs_D_8","account.data_account_type_current_liabilities","l10n_de_skr04.l10n_chart_de_skr04",""
+"chart_skr04_3850","3850","Aufgeschobene Einfuhrumsatzsteuer","l10n_de.tag_de_liabilities_bs_D_8","account.data_account_type_current_liabilities","l10n_de_skr04.l10n_chart_de_skr04","True"
"chart_skr04_3851","3851","Unrichtig oder unberechtigt ausgewiesene Umsatzsteuer","l10n_de.tag_de_asset_bs_B_II_4","account.data_account_type_current_liabilities","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_3854","3854","Steuerzahlungen an andere Länder ","l10n_de.tag_de_liabilities_bs_D_8","account.data_account_type_current_liabilities","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_3860","3860","Verbindlichkeiten aus Umsatzsteuer","l10n_de.tag_de_liabilities_bs_D_8","account.data_account_type_current_liabilities","l10n_de_skr04.l10n_chart_de_skr04",""
@@ -588,6 +588,7 @@
"chart_skr04_4510","4510","Erlöse Abfallverwertung","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_4520","4520","Erlöse Leergut","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_4560","4560","Provisionsumsätze","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr04.l10n_chart_de_skr04",""
+"chart_skr04_4564","4564","Provisionsumsätze (§4Nr.8ff.UStG)","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_4566","4566","Provisionsumsätze 7 % USt","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_4569","4569","Provisionsumsätze 19 % USt","l10n_de.tag_de_pl_01","account.data_account_type_revenue","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_4570","4570","Sonstige Erträge aus Provisionen, Lizenzen und Patenten","l10n_de.tag_de_pl_04","account.data_account_type_revenue","l10n_de_skr04.l10n_chart_de_skr04",""
@@ -726,6 +727,8 @@
"chart_skr04_5130","5130","Einkauf - Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_5160","5160","Einkauf - Roh-, Hilfs- und Betriebsstoffe, innergem. Erwerb 7% VSt und 7% USt","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_5162","5162","Einkauf - Roh-, Hilfs- und Betriebsstoffe, innergem. Erwerb 19% VSt und 19% USt","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
+"chart_skr04_5166","5166","Einkauf Roh-, Hilfs- und Betriebsstoffe, innergemeinschaftlicher Erwerb ohne Vorsteuer und 7 % Umsatzsteuer","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
+"chart_skr04_5167","5167","Einkauf Roh-, Hilfs- und Betriebsstoffe, innergemeinschaftlicher Erwerb ohne Vorsteuer und 19 % Umsatzsteuer","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_5189","5189","Erwerb Roh-, Hilfs- und Betriebsstoffe letzter Abnehmer Dreiecksgeschäft","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_5190","5190","Energiestoffe ( Fertigung )","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_5191","5191","Energiestoffe (Fertigung) 7% Vorsteuer","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
@@ -739,7 +742,11 @@
"chart_skr04_5430","5430","Innergemeinschaftlicher Erwerb ohne Vorsteuerabzug und 7% Umsatzsteuer","","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_5435","5435","Innergemeinschaftlicher Erwerb ohne Vorsteuerabzug und 19% Umsatzsteuer","","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_5440","5440","innergem. Erwerb Neufahrz. o. UStID 19 % VSt und 19 % USt","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
+"chart_skr04_5550","5550","Steuerfreier EG-Erwerb","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
+"chart_skr04_5551","5551","Wareneingang, im Drittland steuerbar (19%)","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
+"chart_skr04_5552","5552","Erwerb 1. Abnehmer im Dreiecksgeschäft","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_5553","5553","Erwerb Waren als letzter Abnehmer innerhalb Dreiecksgeschäft","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
+"chart_skr04_5557","5557","Wareneingang, im Drittland steuerbar (7%)","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_5559","5559","Steuerfreie Einfuhren","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_5600","5600","Nicht abziehbare Vorsteuer","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
"chart_skr04_5610","5610","Nicht abziehbare Vorsteuer 7 %","l10n_de.tag_de_pl_05","account.data_account_type_expenses","l10n_de_skr04.l10n_chart_de_skr04",""
diff --git a/addons/l10n_de_skr04/data/account_tax_fiscal_position_data.xml b/addons/l10n_de_skr04/data/account_tax_fiscal_position_data.xml
index 83668698edf..408719aa2db 100644
--- a/addons/l10n_de_skr04/data/account_tax_fiscal_position_data.xml
+++ b/addons/l10n_de_skr04/data/account_tax_fiscal_position_data.xml
@@ -77,7 +77,74 @@
<field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_93')])]"/>
<field name="tax_group_id" ref="tax_group_0"/>
</record>
-
+ <record id="tax_eu_19_purchase_ust_no_vst_skr04" model="account.tax.template">
+ <field name="sequence">97</field>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ <field name="name">19% Umsatzsteuer aus innergem. Erwerb (kein Vorsteuerabzug)</field>
+ <field name="description">19% USt innerg. Erw. - 0% Vorsteuer</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">-19</field>
+ <field name="refund_account_id" ref="chart_skr04_3809"/>
+ <field name="account_id" ref="chart_skr04_3809"/>
+ <field name="type_tax_use">none</field>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_89')])]"/>
+ <field name="tax_group_id" ref="tax_group_19"/>
+ </record>
+ <record id="tax_eu_19_purchase_vst_no_vst_skr04" model="account.tax.template">
+ <field name="sequence">97</field>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ <field name="name">19% Vorsteuer aus innergem. Erwerb (kein Vorsteuerabzug)</field>
+ <field name="description">19% VSt innerg. Erw. - 0% Vorsteuer</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">19</field>
+ <field name="type_tax_use">none</field>
+ <field name="tax_group_id" ref="tax_group_19"/>
+ </record>
+ <record id="tax_eu_19_purchase_no_vst_skr04" model="account.tax.template">
+ <field name="sequence">20</field>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ <field name="name">Innergem. Erwerb 19%USt/0%VSt</field>
+ <field name="description">innergem. Erwerb 19% - 0% Vorsteuer</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="children_tax_ids" eval="[(6,0,[ref('tax_eu_19_purchase_ust_no_vst_skr04'), ref('tax_eu_19_purchase_vst_no_vst_skr04')])]"/>
+ <field name="tax_group_id" ref="tax_group_19"/>
+ </record>
+ <record id="tax_eu_7_purchase_ust_no_vst_skr04" model="account.tax.template">
+ <field name="sequence">97</field>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ <field name="name">7% Umsatzsteuer aus innergem. Erwerb (kein Vorsteuerabzug)</field>
+ <field name="description">7% USt innerg. Erw. - 0% Vorsteuer</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">-7</field>
+ <field name="refund_account_id" ref="chart_skr04_3809"/>
+ <field name="account_id" ref="chart_skr04_3809"/>
+ <field name="type_tax_use">none</field>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_93')])]"/>
+ <field name="tax_group_id" ref="tax_group_7"/>
+ </record>
+ <record id="tax_eu_7_purchase_vst_no_vst_skr04" model="account.tax.template">
+ <field name="sequence">97</field>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ <field name="name">7% Vorsteuer aus innergem. Erwerb (kein Vorsteuerabzug)</field>
+ <field name="description">7% VSt innerg. Erw. - 0% Vorsteuer</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">7</field>
+ <field name="type_tax_use">none</field>
+ <field name="tax_group_id" ref="tax_group_7"/>
+ </record>
+ <record id="tax_eu_7_purchase_no_vst_skr04" model="account.tax.template">
+ <field name="sequence">20</field>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ <field name="name">Innergem. Erwerb 7%USt/0%VSt</field>
+ <field name="description">innergem. Erwerb 7% - 0% Vorsteuer</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="children_tax_ids" eval="[(6,0,[ref('tax_eu_7_purchase_ust_no_vst_skr04'), ref('tax_eu_7_purchase_vst_no_vst_skr04')])]"/>
+ <field name="tax_group_id" ref="tax_group_7"/>
+ </record>
<record id="tax_eu_car_purchase_ust_skr04" model="account.tax.template">
<field name="sequence">99</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -161,31 +228,77 @@
<field name="tax_group_id" ref="tax_group_0"/>
</record>
<record id="tax_import_19_skr04" model="account.tax.template">
- <field name="sequence">20</field>
+ <field name="sequence">98</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- <field name="name">19% Einfuhrumsatzsteuer</field>
- <field name="description">19% EUSt</field>
+ <field name="name">19% Einfuhrumsatzsteuer (abzugsf. Vorsteuer)</field>
+ <field name="description">19% EUSt (abzugsf. Vorsteuer)</field>
<field name="amount_type">percent</field>
<field name="amount">19</field>
<field name="refund_account_id" ref="chart_skr04_1433"/>
<field name="account_id" ref="chart_skr04_1433"/>
- <field name="type_tax_use">purchase</field>
+ <field name="type_tax_use">none</field>
<field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_62')])]"/>
<field name="tax_group_id" ref="tax_group_19"/>
</record>
- <record id="tax_import_7_skr04" model="account.tax.template">
+ <record id="tax_import_19_payable_skr04" model="account.tax.template">
+ <field name="sequence">98</field>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ <field name="name">19% Einfuhrumsatzsteuer (zu bezahlender Aufschub)</field>
+ <field name="description">19% EUSt (zu bezahlender Aufschub)</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">-19</field>
+ <field name="refund_account_id" ref="chart_skr04_3850"/>
+ <field name="account_id" ref="chart_skr04_3850"/>
+ <field name="type_tax_use">none</field>
+ <field name="tax_group_id" ref="tax_group_19"/>
+ </record>
+ <record id="tax_import_19_and_payable_skr04" model="account.tax.template">
<field name="sequence">21</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- <field name="name">7% Einfuhrumsatzsteuer</field>
- <field name="description">7% EUSt</field>
+ <field name="name">19% Einfuhrumsatzsteuer (§21 Abs.3 UstG)</field>
+ <field name="description">Einfuhrumsatzsteuer 19%</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="children_tax_ids" eval="[(6,0,[ref('tax_import_19_skr04'), ref('tax_import_19_payable_skr04')])]"/>
+ <field name="tax_group_id" ref="tax_group_19"/>
+ </record>
+ <record id="tax_import_7_skr04" model="account.tax.template">
+ <field name="sequence">98</field>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ <field name="name">7% Einfuhrumsatzsteuer (abzugsf. Vorsteuer)</field>
+ <field name="description">7% EUSt (abzugsf. Vorsteuer)</field>
<field name="amount_type">percent</field>
<field name="amount">7</field>
<field name="refund_account_id" ref="chart_skr04_1433"/>
<field name="account_id" ref="chart_skr04_1433"/>
- <field name="type_tax_use">purchase</field>
+ <field name="type_tax_use">none</field>
<field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_62')])]"/>
<field name="tax_group_id" ref="tax_group_7"/>
</record>
+ <record id="tax_import_7_payable_skr04" model="account.tax.template">
+ <field name="sequence">98</field>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ <field name="name">7% Einfuhrumsatzsteuer (zu bezahlender Aufschub)</field>
+ <field name="description">7% EUSt (zu bezahlender Aufschub)</field>
+ <field name="amount_type">percent</field>
+ <field name="amount">-7</field>
+ <field name="refund_account_id" ref="chart_skr04_3850"/>
+ <field name="account_id" ref="chart_skr04_3850"/>
+ <field name="type_tax_use">none</field>
+ <field name="tax_group_id" ref="tax_group_7"/>
+ </record>
+ <record id="tax_import_7_and_payable_skr04" model="account.tax.template">
+ <field name="sequence">21</field>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ <field name="name">7% Einfuhrumsatzsteuer (§21 Abs.3 UstG)</field>
+ <field name="description">Einfuhrumsatzsteuer 7%</field>
+ <field name="amount_type">group</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="children_tax_ids" eval="[(6,0,[ref('tax_import_7_skr04'), ref('tax_import_7_payable_skr04')])]"/>
+ <field name="tax_group_id" ref="tax_group_7"/>
+ </record>
<record id="tax_eu_purchase_tax_free_skr04" model="account.tax.template">
<field name="sequence">22</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -348,7 +461,7 @@
<field name="account_id" ref="chart_skr04_1401"/>
<field name="type_tax_use">purchase</field>
<field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_66')])]"/>
- <field name="tax_group_id" ref="tax_group_7"/>
+ <field name="tax_group_id" ref="tax_group_7"/>
</record>
<record id="tax_vst_no_ustpflicht_skr04" model="account.tax.template">
<field name="sequence">16</field>
@@ -386,7 +499,7 @@
<field name="type_tax_use">purchase</field>
<field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_66')])]"/>
<field name="tax_group_id" ref="tax_group_7"/>
- <field name="price_include" eval="True"/>
+ <field name="price_include" eval="True"/>
</record>
<record id="tax_vst_55_farmer_skr04" model="account.tax.template">
<field name="sequence">26</field>
@@ -399,7 +512,7 @@
<field name="account_id" ref="chart_skr04_1400"/>
<field name="type_tax_use">purchase</field>
<field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_66')])]"/>
- <field name="tax_group_id" ref="tax_group_55"/>
+ <field name="tax_group_id" ref="tax_group_55"/>
</record>
<record id="tax_vst_107_farmer_skr04" model="account.tax.template">
<field name="sequence">27</field>
@@ -412,7 +525,7 @@
<field name="account_id" ref="chart_skr04_1400"/>
<field name="type_tax_use">purchase</field>
<field name="tag_ids" eval="[(6,0,[ref('l10n_de.tag_de_66')])]"/>
- <field name="tax_group_id" ref="tax_group_107"/>
+ <field name="tax_group_id" ref="tax_group_107"/>
</record>
<record id="tax_ust_19_eu_skr04" model="account.tax.template">
<field name="sequence">21</field>
@@ -965,23 +1078,37 @@
<field name="children_tax_ids" eval="[(6,0,[ref('tax_ust_7_purchase_13a_auslagerung_skr04'), ref('tax_vst_7_purchase_13a_auslagerung_skr04')])]"/>
<field name="tax_group_id" ref="tax_group_0"/>
</record>
+ <record id="fiscal_position_domestic_skr04" model="account.fiscal.position.template">
+ <field name="sequence">1</field>
+ <field name="name">Geschäftspartner Inland</field>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ <field name="auto_apply" eval="True" />
+ <field name="country_id" ref="base.de"></field>
+ </record>
<record id="fiscal_position_non_eu_partner_service_skr04" model="account.fiscal.position.template">
+ <field name="sequence">6</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- <field name="name">Dienstleister Ausland</field>
+ <field name="name">Dienstleister Ausland (Nicht-EU)</field>
</record>
<record id="fiscal_position_non_eu_partner_skr04" model="account.fiscal.position.template">
+ <field name="sequence">5</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- <field name="name">Geschäftspartner Ausland</field>
+ <field name="name">Geschäftspartner Ausland (Nicht-EU)</field>
</record>
<record id="fiscal_position_eu_vat_id_partner_skr04" model="account.fiscal.position.template">
+ <field name="sequence">2</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
<field name="name">Geschäftspartner EU (mit USt-ID)</field>
+ <field name="auto_apply" eval="True" />
+ <field name="country_group_id" ref="base.europe"></field>
</record>
<record id="fiscal_position_eu_vat_id_partner_service_skr04" model="account.fiscal.position.template">
+ <field name="sequence">3</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
<field name="name">Dienstleister EU (mit USt-ID)</field>
</record>
<record id="fiscal_position_eu_no_id_partner_skr04" model="account.fiscal.position.template">
+ <field name="sequence">4</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
<field name="name">Geschäftspartner EU (ohne USt-ID)</field>
</record>
@@ -1017,12 +1144,12 @@
</record>
<record id="chart_skr04_fiscal_position_tax_eu_no_id_purchase_19_skr04" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_eu_no_id_partner_skr04"/>
- <field name="tax_dest_id" ref="tax_import_19_skr04"/>
+ <field name="tax_dest_id" ref="tax_eu_19_purchase_no_vst_skr04"/>
<field name="tax_src_id" ref="tax_vst_19_skr04"/>
</record>
<record id="chart_skr04_fiscal_position_tax_eu_no_id_purchase_7_skr04" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_eu_no_id_partner_skr04"/>
- <field name="tax_dest_id" ref="tax_import_7_skr04"/>
+ <field name="tax_dest_id" ref="tax_eu_7_purchase_no_vst_skr04"/>
<field name="tax_src_id" ref="tax_vst_7_skr04"/>
</record>
<record id="chart_skr04_fiscal_position_tax_eu_no_id_sale_19_skr04" model="account.fiscal.position.tax.template">
@@ -1052,7 +1179,7 @@
</record>
<record id="chart_skr04_fiscal_position_tax_non_eu_purchase_19_skr04" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_non_eu_partner_skr04"/>
- <field name="tax_dest_id" ref="tax_import_19_skr04"/>
+ <field name="tax_dest_id" ref="tax_import_19_and_payable_skr04"/>
<field name="tax_src_id" ref="tax_vst_19_skr04"/>
</record>
<record id="chart_skr04_fiscal_position_tax_non_eu_purchase_services_19_skr04" model="account.fiscal.position.tax.template">
@@ -1062,7 +1189,7 @@
</record>
<record id="chart_skr04_fiscal_position_tax_non_eu_purchase_7_skr04" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_non_eu_partner_skr04"/>
- <field name="tax_dest_id" ref="tax_import_7_skr04"/>
+ <field name="tax_dest_id" ref="tax_import_7_and_payable_skr04"/>
<field name="tax_src_id" ref="tax_vst_7_skr04"/>
</record>
@@ -1134,7 +1261,7 @@
<record id="chart_skr04_fiscal_position_account_non_eu_purchase_19_skr04" model="account.fiscal.position.account.template">
<field name="position_id" ref="fiscal_position_non_eu_partner_skr04"/>
<field name="account_src_id" ref="chart_skr04_5400" />
- <field name="account_dest_id" ref="chart_skr04_5435" />
+ <field name="account_dest_id" ref="chart_skr04_5551" />
</record>
<record id="chart_skr04_fiscal_position_acc_non_eu_purchase_services_19_skr04" model="account.fiscal.position.account.template">
<field name="position_id" ref="fiscal_position_non_eu_partner_service_skr04"/>
@@ -1144,7 +1271,7 @@
<record id="chart_skr04_fiscal_position_account_non_eu_purchase_7_skr04" model="account.fiscal.position.account.template">
<field name="position_id" ref="fiscal_position_non_eu_partner_skr04"/>
<field name="account_src_id" ref="chart_skr04_5300" />
- <field name="account_dest_id" ref="chart_skr04_5430" />
+ <field name="account_dest_id" ref="chart_skr04_5557" />
</record>
<record id="chart_skr04_fiscal_position_account_eu_no_id_sale_19_skr04" model="account.fiscal.position.account.template">
<field name="position_id" ref="fiscal_position_eu_no_id_partner_skr04"/>
@@ -1201,17 +1328,21 @@
<record id="chart_skr04_4125" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_eu_sale_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
+ <record id="chart_skr04_4130" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_3eck_first_skr04')])]"/>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ </record>
<record id="chart_skr04_4135" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_free_newcar_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_4139" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr04_mit_vst')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4140" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr04_ohne_vst')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr04_mit_vst')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4150" model="account.account.template">
@@ -1225,7 +1356,7 @@
<record id="chart_skr04_4165" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr04_ohne_vst')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_4185" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1243,11 +1374,15 @@
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4310" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_7_purchase_ust_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_eu_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4315" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_eu_skr04')])]"/>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ </record>
+ <record id="chart_skr04_4331" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4336" model="account.account.template">
@@ -1255,7 +1390,7 @@
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4337" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_ust_goods_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_free_bau_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4338" model="account.account.template">
@@ -1278,6 +1413,10 @@
<field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
+ <record id="chart_skr04_4564" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr04_ohne_vst')])]"/>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ </record>
<record id="chart_skr04_4566" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_ust_7_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1291,11 +1430,11 @@
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4574" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr04_ohne_vst')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4575" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr04_mit_vst')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4576" model="account.account.template">
@@ -1323,7 +1462,7 @@
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4630" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_7_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_not_taxable_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4639" model="account.account.template">
@@ -1378,6 +1517,14 @@
<field name="tax_ids" eval="[(6, 0, [ref('tax_not_taxable_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
+ <record id="chart_skr04_4702" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr04_mit_vst')])]"/>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ </record>
+ <record id="chart_skr04_4704" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr04_mit_vst')])]"/>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ </record>
<record id="chart_skr04_4705" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_export_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1395,11 +1542,11 @@
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4725" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_7_purchase_ust_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_eu_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4726" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_eu_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4731" model="account.account.template">
@@ -1411,7 +1558,7 @@
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4741" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_ust_goods_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_free_mobil_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4742" model="account.account.template">
@@ -1419,11 +1566,11 @@
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4746" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_7_purchase_ust_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_eu_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4748" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_eu_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4750" model="account.account.template">
@@ -1433,7 +1580,7 @@
<record id="chart_skr04_4760" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_4780" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_ust_7_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1441,7 +1588,7 @@
<record id="chart_skr04_4790" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_4836" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1455,7 +1602,7 @@
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4844" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_third_country_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_export_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4845" model="account.account.template">
@@ -1463,7 +1610,7 @@
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4848" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_sale_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_4852" model="account.account.template">
@@ -1510,10 +1657,18 @@
<field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
+ <record id="chart_skr04_5166" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_7_purchase_no_vst_skr04')])]"/>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ </record>
+ <record id="chart_skr04_5167" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_no_vst_skr04')])]"/>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ </record>
<record id="chart_skr04_5189" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_ust_19_purchase_3eck_last_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_5191" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_7_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1521,11 +1676,11 @@
<record id="chart_skr04_5192" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_19_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_5300" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_7_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_5400" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_19_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1539,17 +1694,25 @@
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_5430" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_7_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_7_purchase_no_vst_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_5435" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_no_vst_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_5440" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_eu_car_purchase_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
+ <record id="chart_skr04_5550" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_purchase_tax_free_skr04')])]"/>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ </record>
+ <record id="chart_skr04_5551" model="account.account.template">
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_import_19_and_payable_skr04')])]"/>
+ <field name="chart_template_id" ref="l10n_chart_de_skr04"/>
+ </record>
<record id="chart_skr04_5553" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_ust_19_purchase_3eck_last_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1585,11 +1748,11 @@
<record id="chart_skr04_5725" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_eu_19_purchase_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_5731" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_7_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_5734" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_7_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1609,11 +1772,11 @@
<record id="chart_skr04_5743" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_eu_7_purchase_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_5750" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_7_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_5754" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_7_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1625,11 +1788,11 @@
<record id="chart_skr04_5760" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_19_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_5780" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_7_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_5784" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_7_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1649,7 +1812,7 @@
<record id="chart_skr04_5792" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_ust_19_purchase_3eck_last_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_5794" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_55_farmer_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1669,7 +1832,7 @@
<record id="chart_skr04_5908" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_7_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_5910" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_ust_vst_7_purchase_13b_bau_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1693,7 +1856,7 @@
<record id="chart_skr04_5925" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_ust_19_purchase_13b_werk_ausland_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_5951" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_vst_ust_19_purchase_13b_werk_ausland_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1721,13 +1884,13 @@
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_6888" model="account.account.template">
- <field name="tax_ids" eval="[(6, 0, [ref('tax_free_eu_skr04')])]"/>
+ <field name="tax_ids" eval="[(6, 0, [ref('tax_eu_sale_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="chart_skr04_6892" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_free_skr04_ohne_vst')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
<record id="chart_skr04_6931" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_ust_7_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
@@ -1747,5 +1910,5 @@
<record id="chart_skr04_6938" model="account.account.template">
<field name="tax_ids" eval="[(6, 0, [ref('tax_ust_19_eu_skr04')])]"/>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
- </record>
+ </record>
</odoo>
diff --git a/addons/l10n_fr_pos_cert/models/__init__.py b/addons/l10n_fr_pos_cert/models/__init__.py
index 76de40a9e62..33657992e1c 100644
--- a/addons/l10n_fr_pos_cert/models/__init__.py
+++ b/addons/l10n_fr_pos_cert/models/__init__.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account_bank_statement
+from . import account_fiscal_position
from . import res_company
from . import pos
diff --git a/addons/l10n_fr_pos_cert/models/account_fiscal_position.py b/addons/l10n_fr_pos_cert/models/account_fiscal_position.py
new file mode 100644
index 00000000000..71b4b12155a
--- /dev/null
+++ b/addons/l10n_fr_pos_cert/models/account_fiscal_position.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from odoo import _, models
+from odoo.exceptions import UserError
+
+
+class AccountFiscalPosition(models.Model):
+ _inherit = "account.fiscal.position"
+
+ def write(self, vals):
+ if "tax_ids" in vals:
+ if self.env["pos.order"].sudo().search_count([("fiscal_position_id", "in", self.ids)]):
+ raise UserError(
+ _(
+ "You cannot modify a fiscal position used in a POS order. "
+ + "You should archive it and create a new one."
+ )
+ )
+ return super(AccountFiscalPosition, self).write(vals)
diff --git a/addons/l10n_ua/__init__.py b/addons/l10n_ua/__init__.py
new file mode 100644
index 00000000000..cacb7c95697
--- /dev/null
+++ b/addons/l10n_ua/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+# Copyright (C) 2019 Bohdan Lisnenko <bohdan.lisnenko@erp.co.ua>, ERP Ukraine
diff --git a/addons/l10n_ua/__manifest__.py b/addons/l10n_ua/__manifest__.py
new file mode 100644
index 00000000000..b2f451de461
--- /dev/null
+++ b/addons/l10n_ua/__manifest__.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+# Copyright (C) 2019 Bohdan Lisnenko <bohdan.lisnenko@erp.co.ua>, ERP Ukraine
+
+{
+ 'name': 'Ukraine - Accounting',
+ 'author': 'ERP Ukraine',
+ 'website': 'https://erp.co.ua',
+ 'version': '1.3',
+ 'description': """
+Ukraine - Chart of accounts.
+============================
+ """,
+ 'category': 'Localization',
+ 'depends': ['account'],
+ 'data': [
+ 'data/account_chart_template.xml',
+ 'data/account.account.template.csv',
+ 'data/account_account_tag_data.xml',
+ 'data/account_tax_tag_data.xml',
+ 'data/account_tax_group_data.xml',
+ 'data/account_tax_template.xml',
+ 'data/account_chart_template_config.xml',
+ ],
+}
diff --git a/addons/l10n_ua/data/account.account.template.csv b/addons/l10n_ua/data/account.account.template.csv
new file mode 100644
index 00000000000..14471cfa320
--- /dev/null
+++ b/addons/l10n_ua/data/account.account.template.csv
@@ -0,0 +1,406 @@
+"id","code","name","user_type_id:id","reconcile","chart_template_id:id"
+"ua_psbp_100",100,"Інвестиційна нерухомість","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_101",101,"Земельні ділянки","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_102",102,"Капітальні витрати на поліпшення земель","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_103",103,"Будинки та споруди","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_104",104,"Машини та обладнання","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_105",105,"Транспортні засоби","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_106",106,"Інструменти, прилади та інвентар","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_107",107,"Тварини","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_108",108,"Багаторічні насадження","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_1091",1091,"Інші основні засоби","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_1092",1092,"Основні засоби призначені для продажу","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_111",111,"Бібліотечні фонди","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_112",112,"Малоцінні необоротні матеріальні активи","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_113",113,"Тимчасові (нетитульні) споруди","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_114",114,"Природні ресурси","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_115",115,"Інвентарна тара","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_116",116,"Предмети прокату","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_1171",1171,"Інші необоротні матеріальні активи","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_1172",1172,"Необоротні матеріальні активи призначені для продажу","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_121",121,"Права користування природними ресурсами","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_122",122,"Права користування майном","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_123",123,"Права на комерційні позначення","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_124",124,"Права на об’єкти промислової власності","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_125",125,"Авторське право та суміжні з ним права","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_127",127,"Інші нематеріальні активи","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_131",131,"Знос основних засобів","account.data_account_type_depreciation",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_132",132,"Знос інших необоротних матеріальних активів","account.data_account_type_depreciation",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_133",133,"Накопичена амортизацiя нематеріальних активів","account.data_account_type_depreciation",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_134",134,"Накопичена амортизація довгострокових біологічних активів","account.data_account_type_depreciation",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_135",135,"Знос інвестиційної нерухомості","account.data_account_type_depreciation",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_141",141,"Інвестиції пов’язаним сторонам за методом обліку участі в капіталі","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_142",142,"Інші інвестиції пов’язаним сторонам","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_143",143,"Інвестиції непов’язаним сторонам","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_151",151,"Капітальне будівництво","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_1521",1521,"Придбання основних засобів","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_1522",1522,"Виготовлення та модернізація основних засобів","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_1531",1531,"Придбання інших необоротних матеріальних активів","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_1532",1532,"Виготовлення та модернізація інших необоротних матеріальних активів","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_1541",1541,"Придбання нематеріальних активів","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_1542",1542,"Виготовлення нематеріальних активів","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_155",155,"Придбання (вирощування) довгострокових біологічних активів","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_161",161,"Довгострокові біологічні активи рослинництва, які оцінені за справедливою вартістю","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_162",162,"Довгострокові біологічні активи рослинництва, які оцінені за первісною вартістю","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_163",163,"Довгострокові біологічні активи тваринництва, які оцінені за справедливою вартістю","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_164",164,"Довгострокові біологічні активи тваринництва, які оцінені за первісною вартістю","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_165",165,"Незрілі довгострокові біологічні активи, які оцінюються за справедливою вартістю","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_166",166,"Незрілі довгострокові біологічні активи, які оцінюються за первісною вартістю","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_17",17,"Відстрочені податкові активи","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_181",181,"Заборгованість за майно, що передано у фінансову оренду","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_182",182,"Довгострокові векселі одержані","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_183",183,"Інша дебіторська заборгованість","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_184",184,"Інші необоротні активи","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_191",191,"Гудвіл при придбаннi","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_192",192,"Негативный гудвил","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_193",193,"Гудвіл при приватизації (корпоратизації)","account.data_account_type_fixed_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_200",200,"Транспортно-заготівельні витрати (матеріали)","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_201",201,"Сировина й матеріали","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_202",202,"Купівельні напівфабрикати та комплектуючі вироби","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_203",203,"Паливо","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_204",204,"Тара й тарні матеріали","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_205",205,"Будівельні матеріали","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_206",206,"Матеріали, передані в переробку","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_207",207,"Запасні частини","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_208",208,"Матеріали сільськогосподарського призначення","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_209",209,"Інші матеріали","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_211",211,"Поточні біологічні активи рослинництва, які оцінені за справедливою вартістю","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_212",212,"Поточні біологічні активи тваринництва, які оцінені за справедливою вартістю","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_213",213,"Поточні біологічні активи тваринництва, які оцінені за первісною вартістю","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_22",22,"Малоцінні та швидкозношувані предмети","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_231",231,"Основне виробництво","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_232",232,"Допоміжні виробництва","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_233",233,"Обслуговуючі виробництва","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_234",234,"Давальницьке виробництво","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_235",235,"Обслуговування і ремонт необоротних активів","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_238",238,"Незавершені будівельні контракти","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_239",239,"Проміжні рахунки","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_24",24,"Брак у виробництві","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_25",25,"Напівфабрикати","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_26",26,"Готова продукція","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_27",27,"Продукція сільськогосподарського виробництва","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_280",280,"Транспортно-заготівельні витрати","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_281",281,"Товари на складі","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_2811",2811,"Товари відвантажені зі складу","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_2812",2812,"Товари отримані на склад","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_282",282,"Товари в торгівлі (за продажною вартістю)","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_283",283,"Товари на комісії","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_284",284,"Тара під товарами","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_285",285,"Торгова націнка","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_286",286,"Необоротні активи та групи вибуття, утримувані для продажу","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_289",289,"Товари в торгівлі (за покупною вартістю)","account.data_account_type_current_assets",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_311",311,"Поточні рахунки в національній валюті","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_312",312,"Поточні рахунки в іноземній валюті","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_313",313,"Інші рахунки в банку в національній валюті","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_314",314,"Інші рахунки в банку в іноземній валюті","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_315",315,"Спеціальні рахунки в національній валюті","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_316",316,"Спеціальні рахунки в іноземній валюті","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_331",331,"Грошові документи в національній валюті","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_332",332,"Грошові документи в іноземній валюті","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_333",333,"Грошові кошти в дорозі в національній валюті","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_334",334,"Грошові кошти в дорозі в іноземній валюті","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_335",335,"Електронні гроші, номіновані в національній валюті","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_341",341,"Короткострокові векселі, одержані в національній валюті","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_342",342,"Короткострокові векселі, одержані в іноземній валюті","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_3511",3511,"Еквіваленти грошових коштів (у національній валюті)","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_3512",3512,"Еквіваленти грошових коштів (в іноземній валюті)","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_352",352,"Інші поточні фінансові інвестиції","account.data_account_type_liquidity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_361",361,"Розрахунки з вітчизняними покупцями","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_362",362,"Розрахунки з іноземними покупцями","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_363",363,"Розрахунки з учасниками ПФГ","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_364",364,"Розрахунки по гарантійному забезпеченню","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_365",365,"Розрахунки з роздрібними покупцями","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_3711",3711,"Розрахунки за виданими авансами (в національній валюті)","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_3712",3712,"Розрахунки за виданими авансами (в іноземній валюті)","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_3721",3721,"Розрахунки з підзвітними особами в національній валюті","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_3722",3722,"Розрахунки з підзвітними особами в іноземній валюті","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_373",373,"Розрахунки за нарахованими доходами","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_374",374,"Розрахунки за претензіями","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_375",375,"Розрахунки за відшкодуванням завданих збитків","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_376",376,"Розрахунки за позиками членам кредитних спілок","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_3771",3771,"Розрахунки з іншими дебіторами (в національній валюті)","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_3772",3772,"Розрахунки з іншими дебіторами (в іноземній валюті)","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_3773",3773,"Розрахунки з робітниками та службовцями по інших операціях","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_378",378,"Розрахунки з державними цільовими фондами","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_379",379,"Розрахунки по операціям з дериватимами","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_381",381,"По заборгованості за товари, роботи, послуги","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_382",382,"По заборгованості за розрахунки з бюджетом","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_383",383,"По заборгованості за розрахунки за виданими авансами","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_384",384,"По заборгованості за розрахунки за нарахованими доходами","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_385",385,"По заборгованості за внутрішні розрахунки","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_386",386,"По іншій дебіторській заборгованості","account.data_account_type_receivable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_39",39,"Витрати майбутніх періодів","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_401",401,"Статутний капітал","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_402",402,"Пайовий капітал","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_403",403,"Інший зареєстрований капітал","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_404",404,"Внески до незареєстрованого статутного капіталу","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_411",411,"Дооцінка (уцінка) основних засобів","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_412",412,"Дооцінка (уцінка) нематеріальних активів","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_413",413,"Дооцінка (уцінка) фінансових інструментів","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_414",414,"Інший капітал у дооцінках","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_421",421,"Емісійний дохід","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_422",422,"Інший вкладений капітал","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_423",423,"Накопичені курсові різниці","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_424",424,"Безоплатно одержані необоротні активи","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_425",425,"Інший додатковий капітал","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_43",43,"Резервний капітал","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_441",441,"Прибуток нерозподілений","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_442",442,"Непокриті збитки","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_443",443,"Прибуток, використаний у звітному періоді","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_451",451,"Вилучені акції","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_452",452,"Вилучені вклади й паї","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_453",453,"Інший вилучений капітал","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_46",46,"Неоплачений капітал","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_471",471,"Забезпечення виплат відпусток","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_472",472,"Додаткове пенсійне забезпечення","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_473",473,"Забезпечення гарантійних зобов’язань","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_474",474,"Забезпечення інших витрат і платежів","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_475",475,"Забезпечення, пов'язане з інвестиційною діяльністю","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_476",476,"Забезпечення, пов'язане з фінансовою діяльністю","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_477",477,"Забезпечення матеріального заохочення","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_478",478,"Забезпечення відновлення земельних ділянок","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_481",481,"Кошти, що вилучено з під оподаткування","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_482",482,"Кошти з бюджету та державних цільових фондів","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_483",483,"Бланодійна допомога","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_484",484,"Інші кошти цільового фінансування","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_491",491,"Технічні резерви","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_492",492,"Резерви із страхування життя","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_493",493,"Частка перестраховиків у технічних резервах","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_494",494,"Частка перестраховиків у резервах із страхування життя","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_495",495,"Результат зміни технічних резервів","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_496",496,"Результат зміни резервів із страхування життя","account.data_account_type_equity",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_501",501,"Довгострокові кредити банків у національній валюті","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_502",502,"Довгострокові кредити банків в іноземній валюті","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_503",503,"Відстрочені довгострокові кредити банків у національній валюті","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_504",504,"Відстрочені довгострокові кредити банків в іноземній валюті","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_505",505,"Інші довгострокові позики в національній валюті","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_506",506,"Інші довгострокові позики в іноземній валюті","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_511",511,"Довгострокові векселі, видані в національній валюті","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_512",512,"Довгострокові векселі, видані в іноземній валюті","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_521",521,"Зобов’язання за облігаціями","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_522",522,"Премія за випущеними облігаціями","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_523",523,"Дисконт за випущеними облігаціями","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_531",531,"Зобов’язання з фінансової оренди","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_532",532,"Зобов’язання з оренди цілісних майнових комплексів","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_54",54,"Відстрочені податкові зобов’язання","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_55",55,"Інші довгострокові зобов’язання","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_601",601,"Короткострокові кредити банків у національній валюті","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_602",602,"Короткострокові кредити банків в іноземній валюті","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_603",603,"Відстрочені короткострокові кредити банків у національній валюті","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_604",604,"Відстрочені короткострокові кредити банків в іноземній валюті","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_605",605,"Прострочені позики в національній валюті","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_606",606,"Прострочені позики в іноземній валюті","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_611",611,"Поточна заборгованість за довгостроковими зобов’язаннями в національній валюті","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_612",612,"Поточна заборгованість за довгостроковими зобов’язаннями в іноземній валюті","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_621",621,"Короткострокові векселі, видані в національній валюті","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_622",622,"Короткострокові векселі, видані в іноземній валюті","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_631",631,"Розрахунки з вітчизняними постачальниками","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_632",632,"Розрахунки з іноземними постачальниками","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_633",633,"Розрахунки з учасниками ПФГ","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6411",6411,"Розрахунки за ПДФО","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6412",6412,"Розрахунки за ПДВ","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_64121",64121,"Розрахунки за ПДВ в системі ЕА ПДВ","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_64122",64122,"Залишок на лицьовомцу рахунку з ПДВ після 01.02.2015 року","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_64123",64123,"Від’ємний залишок з ПДВ до 01.02.2015 року","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6413",6413,"Розрахунки за податком на прибуток","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6414",6414,"Розрахунки по єдиному податку","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6415",6415,"Розрахунки по військовому збору","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6416",6416,"Розрахунки за акцизом","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_642",642,"Розрахунки по обов’язкових платежах","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6431",6431,"Податкові зобов’язання","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6432",6432,"Податкові зобов’язання непідтверджені","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6433",6433,"Коригування податкових зобов’язань","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6434",6434,"Податкові зобов’язання (роздріб)","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6435",6435,"Умовний продаж","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6441",6441,"Податковий кредит","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6442",6442,"Податковий кредит непідтверджений","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6443",6443,"Коригування податкового кредиту","account.data_account_type_current_liabilities",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_651",651,"За розрахунками із загальнообов'язкового державного соціального страхування","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_652",652,"За соціальним страхуванням","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_653",653,"За страхуванням на випадок безробіття (не використовується)","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_654",654,"За індивідуальним страхуванням","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_655",655,"За страхуванням майна","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_656",656,"За страхуванням від нещасних випадків","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_661",661,"Розрахунки за заробітною платою","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_662",662,"Розрахунки з депонентами","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_663",663,"Розрахунки за іншими виплатами","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_671",671,"Розрахунки за нарахованими дивідендами","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_672",672,"Розрахунки за іншими виплатами","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_680",680,"Розрахунки, пов'язані з необоротними активами та групами вибуття, утримуваними для продажу","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6811",6811,"Розрахунки за авансами одержаними в національній валюті","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6812",6812,"Розрахунки за авансами одержаними в іноземній валюті","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_682",682,"Внутрішні розрахунки","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_683",683,"Внутрішньогосподарські розрахунки","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_684",684,"Розрахунки за нарахованими відсотками","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6851",6851,"Розрахунки з іншими кредиторами (в національній валюті)","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6852",6852,"Розрахунки з іншими кредиторами (в іноземній валюті)","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_6853",6853,"Розрахунки по виконавчих листах","account.data_account_type_payable",TRUE,"l10n_ua_psbo_chart_template"
+"ua_psbp_69",69,"Доходи майбутніх періодів","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_701",701,"Дохід від реалізації готової продукції","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_702",702,"Дохід від реалізації товарів","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_703",703,"Дохід від реалізації робіт і послуг","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_704",704,"Вирахування з доходу","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_705",705,"Перестрахування","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_7091",7091,"Дохід від роздрібного продажу","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_7092",7092,"Вирахування з доходу від роздрібного продажу","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_710",710,"Дохід від первісного визнання та від зміни вартості активів, які обліковуються за справедливою вартістю","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_711",711,"Дохід від купівлі-продажу іноземної валюти","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_712",712,"Дохід від реалізації інших оборотних активів","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_713",713,"Дохід від операційної оренди активів","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_714",714,"Дохід від операційної курсової різниці","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_715",715,"Одержані штрафи, пені, неустойки","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_716",716,"Відшкодування раніше списаних активів","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_717",717,"Дохід від списання кредиторської заборгованості","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_718",718,"Дохiд вiд безоплатно одержаних оборотних активiв","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_719",719,"Інші доходи від операційної діяльності","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_721",721,"Дохід від інвестицій в асоційовані підприємства","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_722",722,"Дохід від спільної діяльності","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_723",723,"Дохід від інвестицій в дочірні підприємства","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_731",731,"Дивіденди одержані","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_732",732,"Відсотки одержані","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_733",733,"Інші доходи від фінансових операцій","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_740",740,"Дохід від зміни вартості фінансових інструментів","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_741",741,"Дохід від реалізації фінансових інвестицій","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_742",742,"Дохід від відновлення корисності активів","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_743",743,"Доход от реализации имущественных комплексов","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_744",744,"Дохід від неопераційної курсової різниці","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_745",745,"Дохід від безоплатно одержаних активів","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_746",746,"Інші доходи","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_751",751,"Відшкодування збитків від надзвичайних подій","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_752",752,"Інші надзвичайні доходи","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_76",76,"Страхові платежі","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_791",791,"Результат операцiйної діяльності","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_792",792,"Результат фінансових операцій","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_793",793,"Результат іншої звичайної діяльності","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_794",794,"(не використовується) Результат надзвичайних подій","account.data_account_type_revenue",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_801",801,"Витрати сировини й матеріалів","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_802",802,"Витрати купівельних напівфабрикатів та комплектуючих виробів","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_803",803,"Витрати палива й енергії","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_804",804,"Витрати тари й тарних матеріалів","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_805",805,"Витрати будівельних матеріалів","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_806",806,"Витрати запасних частин","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_807",807,"Витрати матеріалів сільськогосподарського призначення","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_808",808,"Витрати товарів","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_809",809,"Інші матеріальні витрати","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_811",811,"Виплати за окладами й тарифами","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_812",812,"Премії та заохочення","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_813",813,"Компенсаційні виплати","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_814",814,"Оплата відпусток","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_815",815,"Оплата іншого невідпрацьованого часу","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_816",816,"Інші витрати на оплату праці","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_821",821,"Відрахування на загальнообов'язкове державне соціальне страхування","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_822",822,"Відрахування на соціальне страхування (не використовується)","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_823",823,"Страхування на випадок безробіття (не використовується)","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_824",824,"Відрахування на індивідуальне страхування","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_831",831,"Амортизація основних засобів","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_832",832,"Амортизація інших необоротних матеріальних активів","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_833",833,"Амортизація нематеріальних активів","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_84",84,"Інші операційні витрати","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_851",851,"Інші затрати за елементами, податок на прибуток","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_852",852,"(не використовується) Інші затрати за елементами, надзвичайні затрати","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_901",901,"Собівартість реалізованої готової продукції","account.data_account_type_direct_costs",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_902",902,"Собівартість реалізованих товарів","account.data_account_type_direct_costs",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_903",903,"Собівартість реалізованих робіт і послуг","account.data_account_type_direct_costs",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_904",904,"Страховi виплати","account.data_account_type_direct_costs",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_91",91,"Загальновиробничі витрати","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_92",92,"Адміністративні витрати","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_93",93,"Витрати на збут","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_940",940,"Витрати від первісного визнання та від зміни вартості активів, які обліковуються за справедливою вартістю","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_941",941,"Витрати на дослідження і розробки","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_942",942,"Витрати на купівлю-продаж іноземної валюти","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_943",943,"Собівартість реалізованих виробничих запасів","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_944",944,"Сумнівні та безнадійні борги","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_945",945,"Втрати від операційної курсової різниці","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_946",946,"Втрати від знецінення запасів","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_947",947,"Нестачі і втрати від псування цінностей","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_948",948,"Визнані штрафи, пені, неустойки","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_949",949,"Інші витрати операційної діяльності","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_951",951,"Відсотки за кредит","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_952",952,"Інші фінансові витрати","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_961",961,"Втрати від інвестицій в асоційовані підприємства","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_962",962,"Втрати від спільної діяльності","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_963",963,"Втрати від інвестицій в дочірні підприємства","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_970",970,"Витрати від зміни вартості фінансових інструментів","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_971",971,"Собівартість реалізованих фінансових інвестицій","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_972",972,"Втрати від зменшення корисності активів","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_973",973,"Себестоимость реализованных имущественных комплексов","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_974",974,"Втрати від неопераційних курсових різниць","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_975",975,"Уцінка необоротних активів і фінансових інвестицій","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_976",976,"Списання необоротних активів","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_977",977,"Інші витрати діяльності","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_psbp_981",981,"Податок на прибуток від усіх видів діяльності","account.data_account_type_expenses",FALSE,"l10n_ua_psbo_chart_template"
+"ua_ias_1000",1000,"Назви брендів","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1001",1001,"Заголовки та назви видань","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1002",1002,"Комп’ютерне програмне забезпечення","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1003",1003,"Ліцензії та привілеї","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1004",1004,"Авторські права, патенти, та інші права","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1005",1005,"Нематеріальні активи на етапі розробки","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1009",1009,"Амортизація нематеріальних активів","account.data_account_type_depreciation",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1010",1010,"Земля","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1011",1011,"Будівлі","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1012",1012,"Машини та обладнання","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1013",1013,"Автомобілі","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1014",1014,"Меблі та приладдя","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1015",1015,"Офісне обладнання","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1019",1019,"Амортизація основних засобів","account.data_account_type_depreciation",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1020",1020,"Гудвіл","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1030",1030,"Біологічні активи","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1040",1040,"Інвестиції","account.data_account_type_fixed_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1100",1100,"Запаси (Склад)","account.data_account_type_current_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1101",1101,"Незавершене виробництво","account.data_account_type_current_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1102",1102,"Готова продукція","account.data_account_type_current_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1120",1120,"Дебіторська заборгованість за товари та послуги","account.data_account_type_receivable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1121",1121,"Відвантажено зі складу без рахунку-фактури","account.data_account_type_current_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1130",1130,"Аванси партнерам","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1131",1131,"Інші оборотні активи","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1140",1140,"Податковий кредит по ПДВ","account.data_account_type_current_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1141",1141,"Аванс по податку на прибуток","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1142",1142,"Кошти на СЕА ПДВ","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1143",1143,"Переплата по лицьовому рахунку з ПДВ","account.data_account_type_current_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1144",1144,"Аванс по ЄСВ","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1150",1150,"Короткострокові фінансові інвестиції","account.data_account_type_current_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1160",1160,"Довгострокові інвестиції","account.data_account_type_non_current_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1170",1170,"Довгострокова дебіторська заборгованість","account.data_account_type_non_current_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1180",1180,"Відстрочені податкові активи","account.data_account_type_non_current_assets",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1200",1200,"Кредиторська заборгованість за товари та послуги","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1201",1201,"Отримано на склад без рахунку-фактури","account.data_account_type_current_liabilities",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1202",1202,"Податок на прибуток","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1203",1203,"ПДВ","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1204",1204,"Податкові зобов’язання по ПДВ","account.data_account_type_current_liabilities",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1205",1205,"ПДФО","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_12051",12051,"Військовий збір","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1206",1206,"ЄСВ","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1207",1207,"Інші податки","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1208",1208,"Заробітна плата та винагороди","account.data_account_type_payable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1210",1210,"Аванси одержані від покупців та замовників","account.data_account_type_receivable",TRUE,"l10n_ua_ias_chart_template"
+"ua_ias_1220",1220,"Інші короткострокові зобов’язання","account.data_account_type_current_liabilities",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1230",1230,"Короткострокові позики","account.data_account_type_current_liabilities",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1240",1240,"Короткострокова частка за довгострокові позики","account.data_account_type_current_liabilities",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1300",1300,"Довгострокові позики","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1301",1301,"Довгострокові кредити","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1302",1302,"Іпотека","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1310",1310,"Відстрочені податкові зобов’язання","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1320",1320,"Витрати майбутніх періодів","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1330",1330,"Інші довгострокові зобов’язання","account.data_account_type_non_current_liabilities",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1400",1400,"Статутний капітал","account.data_account_type_equity",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1401",1401,"Емісійний фонд","account.data_account_type_equity",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1402",1402,"Вилучений капітал","account.data_account_type_equity",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1403",1403,"Додатковий оплачений капітал","account.data_account_type_equity",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1404",1404,"Резерв переоцінки","account.data_account_type_equity",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1405",1405,"Неоплачений капітал","account.data_account_type_equity",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1406",1406,"Нерозподілений прибуток (непокритий збиток)","account.data_account_type_equity",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1407",1407,"Частка меншості","account.data_account_type_equity",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_1408",1408,"Ефект курсових різниць","account.data_account_type_equity",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_2000",2000,"Дохід від продажу (категорія № 1)","account.data_account_type_revenue",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_2001",2001,"Дохід від продажу (категорія № 2)","account.data_account_type_revenue",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_2002",2002,"Дохід від продажу (категорія № 3)","account.data_account_type_revenue",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_2003",2003,"Дохід від продажу (категорія № 4)","account.data_account_type_revenue",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_2100",2100,"Фінансовий результат","account.data_unaffected_earnings",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_2200",2200,"Собівартість продажу (категорія № 1)","account.data_account_type_direct_costs",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_2201",2201,"Собівартість продажу (категорія № 2)","account.data_account_type_direct_costs",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_2202",2202,"Собівартість продажу (категорія № 3)","account.data_account_type_direct_costs",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_2203",2203,"Собівартість продажу (категорія № 4)","account.data_account_type_direct_costs",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_2300",2300,"Адміністративні витрати","account.data_account_type_expenses",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_2400",2400,"Витрати на збут","account.data_account_type_expenses",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_2500",2500,"Інші витрати","account.data_account_type_expenses",FALSE,"l10n_ua_ias_chart_template"
+"ua_ias_2600",2600,"Податок на прибуток","account.data_account_type_expenses",FALSE,"l10n_ua_ias_chart_template"
diff --git a/addons/l10n_ua/data/account_account_tag_data.xml b/addons/l10n_ua/data/account_account_tag_data.xml
new file mode 100644
index 00000000000..9cb77da24e2
--- /dev/null
+++ b/addons/l10n_ua/data/account_account_tag_data.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data noupdate="1">
+ <record id="acc_tag_vat" model="account.account.tag">
+ <field name="name">ПДВ</field>
+ <field name="applicability">accounts</field>
+ </record>
+ </data>
+</odoo>
diff --git a/addons/l10n_ua/data/account_chart_template.xml b/addons/l10n_ua/data/account_chart_template.xml
new file mode 100644
index 00000000000..f84159b9d01
--- /dev/null
+++ b/addons/l10n_ua/data/account_chart_template.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+
+ <record id="l10n_ua_psbo_chart_template" model="account.chart.template">
+ <field name="name">План рахунків ПСБО</field>
+ <field name="cash_account_code_prefix">301</field>
+ <field name="bank_account_code_prefix">311</field>
+ <field name="transfer_account_code_prefix">333</field>
+ <field name="code_digits">6</field>
+ <field name="currency_id" ref="base.UAH"/>
+ </record>
+
+ <record id="l10n_ua_ias_chart_template" model="account.chart.template">
+ <field name="name">План рахунків МСФЗ</field>
+ <field name="bank_account_code_prefix">1112</field>
+ <field name="cash_account_code_prefix">1111</field>
+ <field name="transfer_account_code_prefix">1119</field>
+ <field name="code_digits">6</field>
+ <field name="currency_id" ref="base.UAH"/>
+ </record>
+</odoo>
diff --git a/addons/l10n_ua/data/account_chart_template_config.xml b/addons/l10n_ua/data/account_chart_template_config.xml
new file mode 100644
index 00000000000..b8901fd547f
--- /dev/null
+++ b/addons/l10n_ua/data/account_chart_template_config.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+ <data noupdate="1">
+ <record id="l10n_ua_psbo_chart_template" model="account.chart.template">
+ <field name="property_account_receivable_id" ref="ua_psbp_361"/>
+ <field name="property_account_payable_id" ref="ua_psbp_631"/>
+ <field name="property_account_expense_categ_id" ref="ua_psbp_901"/>
+ <field name="property_account_income_categ_id" ref="ua_psbp_701"/>
+ <field name="complete_tax_set" eval="True"/>
+ <field name="use_anglo_saxon" eval="True"/>
+ <field name="property_stock_account_input_categ_id" ref="ua_psbp_2812"/>
+ <field name="property_stock_account_output_categ_id" ref="ua_psbp_2811"/>
+ <field name="property_stock_valuation_account_id" ref="ua_psbp_281"/>
+ <field name="income_currency_exchange_account_id" ref="ua_psbp_711"/>
+ <field name="expense_currency_exchange_account_id" ref="ua_psbp_942"/>
+ </record>
+
+ <record id="l10n_ua_ias_chart_template" model="account.chart.template">
+ <field name="property_account_receivable_id" ref="ua_ias_1120"/>
+ <field name="property_account_payable_id" ref="ua_ias_1200"/>
+ <field name="property_account_expense_categ_id" ref="ua_ias_2200"/>
+ <field name="property_account_income_categ_id" ref="ua_ias_2000"/>
+ <field name="complete_tax_set" eval="True"/>
+ <field name="use_anglo_saxon" eval="True"/>
+ <field name="property_stock_account_input_categ_id" ref="ua_ias_1201"/>
+ <field name="property_stock_account_output_categ_id" ref="ua_ias_1121"/>
+ <field name="property_stock_valuation_account_id" ref="ua_ias_1100"/>
+ <field name="income_currency_exchange_account_id" ref="ua_ias_2100"/>
+ <field name="expense_currency_exchange_account_id" ref="ua_ias_2500"/>
+ </record>
+
+ <function model="account.chart.template" name="try_loading_for_current_company">
+ <value eval="[ref('l10n_ua.l10n_ua_psbo_chart_template')]"/>
+ </function>
+ </data>
+</odoo>
diff --git a/addons/l10n_ua/data/account_tax_group_data.xml b/addons/l10n_ua/data/account_tax_group_data.xml
new file mode 100644
index 00000000000..62ad5657896
--- /dev/null
+++ b/addons/l10n_ua/data/account_tax_group_data.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data noupdate="1">
+ <record id="tax_group_vat20" model="account.tax.group">
+ <field name="name">ПДВ 20%</field>
+ </record>
+ <record id="tax_group_vat7" model="account.tax.group">
+ <field name="name">ПДВ 7%</field>
+ </record>
+ <record id="tax_group_vat0" model="account.tax.group">
+ <field name="name">ПДВ 0%</field>
+ </record>
+ <record id="tax_group_vat_free" model="account.tax.group">
+ <field name="name">Звільнено від ПДВ</field>
+ </record>
+ <record id="tax_group_not_vat" model="account.tax.group">
+ <field name="name">Не є ПДВ</field>
+ </record>
+ </data>
+</odoo>
diff --git a/addons/l10n_ua/data/account_tax_tag_data.xml b/addons/l10n_ua/data/account_tax_tag_data.xml
new file mode 100644
index 00000000000..ce86c0f17b0
--- /dev/null
+++ b/addons/l10n_ua/data/account_tax_tag_data.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data noupdate="1">
+ <record id="tax_tag_vat20" model="account.account.tag">
+ <field name="name">ПДВ 20%</field>
+ <field name="applicability">taxes</field>
+ </record>
+ <record id="tax_tag_vat7" model="account.account.tag">
+ <field name="name">ПДВ 7%</field>
+ <field name="applicability">taxes</field>
+ </record>
+ <record id="tax_tag_vat0" model="account.account.tag">
+ <field name="name">ПДВ 0%</field>
+ <field name="applicability">taxes</field>
+ </record>
+ <record id="tax_tag_vat_free" model="account.account.tag">
+ <field name="name">Звільнено від ПДВ</field>
+ <field name="applicability">taxes</field>
+ </record>
+ <record id="tax_tag_vat_not" model="account.account.tag">
+ <field name="name">Не є об'єктом ПДВ</field>
+ <field name="applicability">taxes</field>
+ </record>
+ </data>
+</odoo>
diff --git a/addons/l10n_ua/data/account_tax_template.xml b/addons/l10n_ua/data/account_tax_template.xml
new file mode 100644
index 00000000000..e165b044002
--- /dev/null
+++ b/addons/l10n_ua/data/account_tax_template.xml
@@ -0,0 +1,485 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+ <data noupdate="0">
+ <record id="ua_psbp_6412" model="account.account.template">
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.acc_tag_vat')])]"/>
+ </record>
+
+ <!-- Tax template for VAT -->
+ <record id="sale_tax_template_vat20_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">10</field>
+ <field name="name">Реалізація ПДВ 20%</field>
+ <field name="description">+ ПДВ 20%</field>
+ <field name="amount">20</field>
+ <field name="type_tax_use">sale</field>
+ <field name="account_id" ref="ua_psbp_6431"/>
+ <field name="refund_account_id" ref="ua_psbp_6431"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat20')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat20"/>
+ </record>
+ <record id="sale_tax_template_vat20incl_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">10</field>
+ <field name="name">Реалізація в т. ч. ПДВ 20%</field>
+ <field name="description">в т. ч. ПДВ 20%</field>
+ <field name="amount">20</field>
+ <field name="type_tax_use">sale</field>
+ <field name="price_include" eval="1"/>
+ <field name="account_id" ref="ua_psbp_6431"/>
+ <field name="refund_account_id" ref="ua_psbp_6431"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat20')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat20"/>
+ </record>
+ <record id="sale_tax_template_vat7_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">11</field>
+ <field name="name">Реалізація ПДВ 7%</field>
+ <field name="description">+ ПДВ 7%</field>
+ <field name="amount">7</field>
+ <field name="type_tax_use">sale</field>
+ <field name="account_id" ref="ua_psbp_6431"/>
+ <field name="refund_account_id" ref="ua_psbp_6431"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat7')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat7"/>
+ </record>
+ <record id="sale_tax_template_vat7incl_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">11</field>
+ <field name="name">Реалізація в т. ч. ПДВ 7%</field>
+ <field name="description">в т .ч. ПДВ 7%</field>
+ <field name="amount">7</field>
+ <field name="type_tax_use">sale</field>
+ <field name="price_include" eval="1"/>
+ <field name="account_id" ref="ua_psbp_6431"/>
+ <field name="refund_account_id" ref="ua_psbp_6431"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat7')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat7"/>
+ </record>
+ <record id="sale_tax_template_vat0_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">12</field>
+ <field name="name">Реалізація ПДВ 0%</field>
+ <field name="description">ПДВ 0%</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ <field name="account_id" ref="ua_psbp_6431"/>
+ <field name="refund_account_id" ref="ua_psbp_6431"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat0')])]"/>
+ </record>
+ <record id="sale_tax_template_vat_free_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">13</field>
+ <field name="name">Реалізація звільнена від ПДВ</field>
+ <field name="description">Звільнено від ПДВ</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ <field name="account_id" ref="ua_psbp_6431"/>
+ <field name="refund_account_id" ref="ua_psbp_6431"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat_free')])]"/>
+ </record>
+ <record id="sale_tax_template_vat_not_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">14</field>
+ <field name="name">Реалізація Не є об'єктом ПДВ</field>
+ <field name="description">Не є об'єктом ПДВ</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ <field name="account_id" ref="ua_psbp_6431"/>
+ <field name="refund_account_id" ref="ua_psbp_6431"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat_not')])]"/>
+ </record>
+
+ <record id="purchase_tax_template_vat20_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">20</field>
+ <field name="name">Придбання ПДВ 20%</field>
+ <field name="description">+ ПДВ 20%</field>
+ <field name="amount">20</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="account_id" ref="ua_psbp_6441"/>
+ <field name="refund_account_id" ref="ua_psbp_6441"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat20')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat20"/>
+ </record>
+ <record id="purchase_tax_template_vat20incl_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">20</field>
+ <field name="name">Придбання в т. ч. ПДВ 20%</field>
+ <field name="description">в т. ч. ПДВ 20%</field>
+ <field name="amount">20</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="price_include" eval="1"/>
+ <field name="account_id" ref="ua_psbp_6441"/>
+ <field name="refund_account_id" ref="ua_psbp_6441"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat20')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat20"/>
+ </record>
+ <record id="purchase_tax_template_vat7_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">21</field>
+ <field name="name">Придбання ПДВ 7%</field>
+ <field name="description">+ ПДВ 7%</field>
+ <field name="amount">7</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="account_id" ref="ua_psbp_6441"/>
+ <field name="refund_account_id" ref="ua_psbp_6441"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat7')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat7"/>
+ </record>
+ <record id="purchase_tax_template_vat7incl_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">21</field>
+ <field name="name">Придбання в т. ч. ПДВ 7%</field>
+ <field name="description">в т. ч. ПДВ 7%</field>
+ <field name="amount">7</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="price_include" eval="1"/>
+ <field name="account_id" ref="ua_psbp_6441"/>
+ <field name="refund_account_id" ref="ua_psbp_6441"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat7')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat7"/>
+ </record>
+ <record id="purchase_tax_template_vat0_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">22</field>
+ <field name="name">Придбання ПДВ 0%</field>
+ <field name="description">ПДВ 0%</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="account_id" ref="ua_psbp_6441"/>
+ <field name="refund_account_id" ref="ua_psbp_6441"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat0')])]"/>
+ </record>
+ <record id="purchase_tax_template_vat_free_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">23</field>
+ <field name="name">Придбання звільнене від ПДВ</field>
+ <field name="description">Звільнено від ПДВ</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="account_id" ref="ua_psbp_6441"/>
+ <field name="refund_account_id" ref="ua_psbp_6441"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat_free')])]"/>
+ </record>
+ <record id="purchase_tax_template_vat_not_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">24</field>
+ <field name="name">Придбання Не є об'єктом ПДВ</field>
+ <field name="description">Не є об'єктом ПДВ</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="account_id" ref="ua_psbp_6441"/>
+ <field name="refund_account_id" ref="ua_psbp_6441"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat_not')])]"/>
+ </record>
+ <!-- Simplified tax system -->
+ <!-- Sale taxes -->
+ <record id="simple_tax_sale_product_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">30</field>
+ <field name="name">Дохід від продажу товарів</field>
+ <field name="description">Дохід від продажу товарів</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ </record>
+ <record id="simple_tax_sale_gift_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">31</field>
+ <field name="name">Дохід від безоплатно отриманих товарів</field>
+ <field name="description">Дохід від безоплатно отриманих товарів</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ </record>
+ <record id="simple_tax_sale_old_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">32</field>
+ <field name="name">Дохід від заборгованності за якою минув строк позивної давності</field>
+ <field name="description">Дохід від заборгованності, за якою минув строк позивної давності</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ </record>
+ <record id="simple_tax_sale_15_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">33</field>
+ <field name="name">Дохід, за ставкою 15%</field>
+ <field name="description">Дохід за ставкою 15%</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ </record>
+ <!-- Purchase taxes -->
+ <record id="simple_tax_purchase_product_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">40</field>
+ <field name="name">Витрати від продажу товарів</field>
+ <field name="description">Витрати від продажу товарів</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ </record>
+ <record id="simple_tax_purchase_salary_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">41</field>
+ <field name="name">Витрати на оплату праці</field>
+ <field name="description">Витрати на оплату праці найманих працівників</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ </record>
+ <record id="simple_tax_purchase_esv_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">42</field>
+ <field name="name">Витрати ЄСВ</field>
+ <field name="description">Витрати на ЄСВ</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ </record>
+ <record id="simple_tax_purchase_other_psbo" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_psbo_chart_template"/>
+ <field name="sequence">43</field>
+ <field name="name">Витрати інші</field>
+ <field name="description">Витрати інші</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ </record>
+
+ <!-- IAS -->
+ <record id="ua_ias_1203" model="account.account.template">
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.acc_tag_vat')])]"/>
+ </record>
+
+ <!-- Tax template for VAT -->
+ <record id="sale_tax_template_vat20" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">10</field>
+ <field name="name">Реалізація з ПДВ 20%</field>
+ <field name="description">+ ПДВ 20%</field>
+ <field name="amount">20</field>
+ <field name="type_tax_use">sale</field>
+ <field name="account_id" ref="ua_ias_1204"/>
+ <field name="refund_account_id" ref="ua_ias_1204"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat20')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat20"/>
+ </record>
+ <record id="sale_tax_template_vat20incl" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">10</field>
+ <field name="name">Реалізація в т. ч. ПДВ 20%</field>
+ <field name="description">в т. ч. ПДВ 20%</field>
+ <field name="amount">20</field>
+ <field name="type_tax_use">sale</field>
+ <field name="price_include" eval="1"/>
+ <field name="account_id" ref="ua_ias_1204"/>
+ <field name="refund_account_id" ref="ua_ias_1204"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat20')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat20"/>
+ </record>
+ <record id="sale_tax_template_vat7" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">11</field>
+ <field name="name">Реалізація з ПДВ 7%</field>
+ <field name="description">+ ПДВ 7%</field>
+ <field name="amount">7</field>
+ <field name="type_tax_use">sale</field>
+ <field name="account_id" ref="ua_ias_1204"/>
+ <field name="refund_account_id" ref="ua_ias_1204"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat7')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat7"/>
+ </record>
+ <record id="sale_tax_template_vat7incl" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">11</field>
+ <field name="name">Реалізація в т. ч. ПДВ 7%</field>
+ <field name="description">в т .ч. ПДВ 7%</field>
+ <field name="amount">7</field>
+ <field name="type_tax_use">sale</field>
+ <field name="price_include" eval="1"/>
+ <field name="account_id" ref="ua_ias_1204"/>
+ <field name="refund_account_id" ref="ua_ias_1204"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat7')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat7"/>
+ </record>
+ <record id="sale_tax_template_vat0" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">12</field>
+ <field name="name">Реалізація з ПДВ 0%</field>
+ <field name="description">ПДВ 0%</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ <field name="account_id" ref="ua_ias_1204"/>
+ <field name="refund_account_id" ref="ua_ias_1204"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat0')])]"/>
+ </record>
+ <record id="sale_tax_template_vat_free" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">13</field>
+ <field name="name">Реалізація звільнена від ПДВ</field>
+ <field name="description">Звільнено від ПДВ</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ <field name="account_id" ref="ua_ias_1204"/>
+ <field name="refund_account_id" ref="ua_ias_1204"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat_free')])]"/>
+ </record>
+ <record id="sale_tax_template_vat_not" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">14</field>
+ <field name="name">Реалізація не є об'єктом ПДВ</field>
+ <field name="description">Не є об'єктом ПДВ</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ <field name="account_id" ref="ua_ias_1204"/>
+ <field name="refund_account_id" ref="ua_ias_1204"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat_not')])]"/>
+ </record>
+
+ <record id="purchase_tax_template_vat20" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">20</field>
+ <field name="name">Придбання з ПДВ 20%</field>
+ <field name="description">+ ПДВ 20%</field>
+ <field name="amount">20</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="account_id" ref="ua_ias_1140"/>
+ <field name="refund_account_id" ref="ua_ias_1140"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat20')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat20"/>
+ </record>
+ <record id="purchase_tax_template_vat20incl" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">20</field>
+ <field name="name">Придбання в т. ч. ПДВ 20%</field>
+ <field name="description">в т. ч. ПДВ 20%</field>
+ <field name="amount">20</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="price_include" eval="1"/>
+ <field name="account_id" ref="ua_ias_1140"/>
+ <field name="refund_account_id" ref="ua_ias_1140"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat20')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat20"/>
+ </record>
+ <record id="purchase_tax_template_vat7" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">21</field>
+ <field name="name">Придбання з ПДВ 7%</field>
+ <field name="description">+ ПДВ 7%</field>
+ <field name="amount">7</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="account_id" ref="ua_ias_1140"/>
+ <field name="refund_account_id" ref="ua_ias_1140"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat7')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat7"/>
+ </record>
+ <record id="purchase_tax_template_vat7incl" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">21</field>
+ <field name="name">Придбання в т. ч. ПДВ 7%</field>
+ <field name="description">в т. ч. ПДВ 7%</field>
+ <field name="amount">7</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="price_include" eval="1"/>
+ <field name="account_id" ref="ua_ias_1140"/>
+ <field name="refund_account_id" ref="ua_ias_1140"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat7')])]"/>
+ <field name="tax_group_id" ref="l10n_ua.tax_group_vat7"/>
+ </record>
+ <record id="purchase_tax_template_vat0" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">22</field>
+ <field name="name">Придбання з ПДВ 0%</field>
+ <field name="description">ПДВ 0%</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="account_id" ref="ua_ias_1140"/>
+ <field name="refund_account_id" ref="ua_ias_1140"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat0')])]"/>
+ </record>
+ <record id="purchase_tax_template_vat_free" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">23</field>
+ <field name="name">Придбання звільнене від ПДВ</field>
+ <field name="description">Звільнено від ПДВ</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="account_id" ref="ua_ias_1140"/>
+ <field name="refund_account_id" ref="ua_ias_1140"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat_free')])]"/>
+ </record>
+ <record id="purchase_tax_template_vat_not" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">24</field>
+ <field name="name">Придбання не є об'єктом ПДВ</field>
+ <field name="description">Не є об'єктом ПДВ</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ <field name="account_id" ref="ua_ias_1140"/>
+ <field name="refund_account_id" ref="ua_ias_1140"/>
+ <field name="tag_ids" eval="[(6,0,[ref('l10n_ua.tax_tag_vat_not')])]"/>
+ </record>
+ <!-- Simplified tax system -->
+ <!-- Sale taxes -->
+ <record id="simple_tax_sale_product" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">30</field>
+ <field name="name">Дохід від продажу товарів</field>
+ <field name="description">Дохід від продажу товарів</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ </record>
+ <record id="simple_tax_sale_gift" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">31</field>
+ <field name="name">Дохід від безоплатно отриманих товарів</field>
+ <field name="description">Дохід від безоплатно отриманих товарів</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ </record>
+ <record id="simple_tax_sale_old" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">32</field>
+ <field name="name">Дохід від заборгованності, за якою минув строк позивної давності</field>
+ <field name="description">Дохід від заборгованності, за якою минув строк позивної давності</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ </record>
+ <record id="simple_tax_sale_15" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">33</field>
+ <field name="name">Дохід, що оподатковується за ставкою 15%</field>
+ <field name="description">Дохід за ставкою 15%</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">sale</field>
+ </record>
+ <!-- Purchase taxes -->
+ <record id="simple_tax_purchase_product" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">40</field>
+ <field name="name">Витрати від продажу товарів</field>
+ <field name="description">Витрати від продажу товарів</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ </record>
+ <record id="simple_tax_purchase_salary" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">41</field>
+ <field name="name">Витрати на оплату праці найманих працівників</field>
+ <field name="description">Витрати на оплату праці найманих працівників</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ </record>
+ <record id="simple_tax_purchase_esv" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">42</field>
+ <field name="name">Витрати на ЄСВ</field>
+ <field name="description">Витрати на ЄСВ</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ </record>
+ <record id="simple_tax_purchase_other" model="account.tax.template">
+ <field name="chart_template_id" ref="l10n_ua_ias_chart_template"/>
+ <field name="sequence">43</field>
+ <field name="name">Витрати інші</field>
+ <field name="description">Витрати інші</field>
+ <field name="amount">0</field>
+ <field name="type_tax_use">purchase</field>
+ </record>
+ </data>
+</odoo>
diff --git a/addons/l10n_ua/static/description/icon.png b/addons/l10n_ua/static/description/icon.png
new file mode 100644
index 00000000000..57cf957010d
--- /dev/null
+++ b/addons/l10n_ua/static/description/icon.png
Binary files differ
diff --git a/addons/lunch/report/lunch_product_report.py b/addons/lunch/report/lunch_product_report.py
index 140e5c73a0a..7c9fcfcb000 100644
--- a/addons/lunch/report/lunch_product_report.py
+++ b/addons/lunch/report/lunch_product_report.py
@@ -81,11 +81,13 @@ 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,
- (SELECT date FROM lunch_order WHERE product_id = product.id AND user_id = users.id AND date <= current_date ORDER BY date DESC LIMIT 1) AS last_order_date
+ max(l_order.date) AS 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
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/mail/models/ir_attachment.py b/addons/mail/models/ir_attachment.py
index 7273145096d..4e0cfc4b0ea 100644
--- a/addons/mail/models/ir_attachment.py
+++ b/addons/mail/models/ir_attachment.py
@@ -20,6 +20,8 @@ class IrAttachment(models.Model):
attached to.
"""
self.ensure_one()
+ if not self.res_model:
+ return
related_record = self.env[self.res_model].browse(self.res_id)
# message_main_attachment_id field can be empty, that's why we compare to False;
# we are just checking that it exists on the model before writing it
diff --git a/addons/mail/models/ir_model.py b/addons/mail/models/ir_model.py
index 52efe400df9..f0b873730b0 100644
--- a/addons/mail/models/ir_model.py
+++ b/addons/mail/models/ir_model.py
@@ -28,13 +28,23 @@ class IrModel(models.Model):
query = "DELETE FROM mail_message WHERE model in %s"
self.env.cr.execute(query, [models])
+ # Get files attached solely by the models
query = """
- DELETE FROM ir_attachment
- WHERE res_model in %s
- RETURNING store_fname
+ SELECT DISTINCT store_fname
+ FROM ir_attachment
+ WHERE res_model IN %s
+ EXCEPT
+ SELECT store_fname
+ FROM ir_attachment
+ WHERE res_model not IN %s;
"""
+ self.env.cr.execute(query, [models, models])
+ fnames = self.env.cr.fetchall()
+
+ query = """DELETE FROM ir_attachment WHERE res_model in %s"""
self.env.cr.execute(query, [models])
- for (fname,) in self.env.cr.fetchall():
+
+ for (fname,) in fnames:
self.env['ir.attachment']._file_delete(fname)
return super(IrModel, self).unlink()
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index 1ae85a249ca..b6bbe82407e 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -616,7 +616,7 @@ class Channel(models.Model):
if channel.channel_type == 'chat':
# direct_partner should be removed from channel info since we can find it from members and channel_type
# we keep it know to avoid change tests and javascript
- direct_partner = direct_channel_partners.filtered(lambda pc: pc.partner_id.id != self.env.user.partner_id.id)
+ direct_partner = channel_partners.filtered(lambda pc: pc.partner_id.id != self.env.user.partner_id.id)
info['direct_partner'] = [partner_infos[direct_partner[0].partner_id.id]]
# add needaction and unread counter, since the user is logged
info['message_needaction_counter'] = channel.message_needaction_counter
diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py
index 3b8732d86ac..44cea49b102 100644
--- a/addons/mail/models/mail_template.py
+++ b/addons/mail/models/mail_template.py
@@ -476,9 +476,13 @@ class MailTemplate(models.Model):
report = template.report_template
report_service = report.report_name
- if report.report_type not in ['qweb-html', 'qweb-pdf']:
- raise UserError(_('Unsupported report type %s found.') % report.report_type)
- result, format = report.render_qweb_pdf([res_id])
+ if report.report_type in ['qweb-html', 'qweb-pdf']:
+ result, format = report.render_qweb_pdf([res_id])
+ else:
+ res = report.render([res_id])
+ if not res:
+ raise UserError(_('Unsupported report type %s found.') % report.report_type)
+ result, format = res
# TODO in trunk, change return format to binary to match message_post expected format
result = base64.b64encode(result)
diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py
index 154c9d60b49..81ef7c99da2 100644
--- a/addons/mrp/models/mrp_workorder.py
+++ b/addons/mrp/models/mrp_workorder.py
@@ -353,6 +353,9 @@ class MrpWorkorder(models.Model):
self.button_finish()
return True
+ def _get_byproduct_move_to_update(self):
+ return self.production_id.move_finished_ids.filtered(lambda x: (x.product_id.id != self.production_id.product_id.id) and (x.state not in ('done', 'cancel')))
+
def _create_or_update_finished_line(self):
"""
1. Check that the final lot and the quantity producing is valid regarding
diff --git a/addons/mrp/models/res_config_settings.py b/addons/mrp/models/res_config_settings.py
index b2a4de1a5ed..087d8050b40 100644
--- a/addons/mrp/models/res_config_settings.py
+++ b/addons/mrp/models/res_config_settings.py
@@ -26,4 +26,13 @@ class ResConfigSettings(models.TransientModel):
@api.onchange('group_mrp_routings')
def _onchange_group_mrp_routings(self):
- self.module_mrp_workorder = self.group_mrp_routings
+ # If we activate 'MRP Work Orders', it means that we need to install 'mrp_workorder'.
+ # The opposite is not always true: other modules (such as 'quality_mrp_workorder') may
+ # depend on 'mrp_workorder', so we should not automatically uninstall the module if 'MRP
+ # Work Orders' is deactivated.
+ # Long story short: if 'mrp_workorder' is already installed, we don't uninstall it based on
+ # group_mrp_routings
+ if self.group_mrp_routings:
+ self.module_mrp_workorder = True
+ elif not self.env['ir.module.module'].search([('name', '=', 'mrp_workorder'), ('state', '=', 'installed')]):
+ self.module_mrp_workorder = False
diff --git a/addons/mrp/models/stock_rule.py b/addons/mrp/models/stock_rule.py
index 8f72e133c24..322746a327d 100644
--- a/addons/mrp/models/stock_rule.py
+++ b/addons/mrp/models/stock_rule.py
@@ -6,6 +6,7 @@ from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError
+from odoo.osv import expression
class StockRule(models.Model):
@@ -132,3 +133,8 @@ class ProcurementGroup(models.Model):
else:
procurements_without_kit.append(procurement)
return super(ProcurementGroup, self).run(procurements_without_kit)
+
+ def _get_moves_to_assign_domain(self):
+ domain = super(ProcurementGroup, self)._get_moves_to_assign_domain()
+ domain = expression.AND([domain, [('production_id', '=', False)]])
+ return domain
diff --git a/addons/mrp/models/stock_warehouse.py b/addons/mrp/models/stock_warehouse.py
index de45d667b39..1cc46635b73 100644
--- a/addons/mrp/models/stock_warehouse.py
+++ b/addons/mrp/models/stock_warehouse.py
@@ -170,18 +170,19 @@ class StockWarehouse(models.Model):
manufacture_steps = vals.get('manufacture_steps', def_values['manufacture_steps'])
code = vals.get('code') or code
code = code.replace(' ', '').upper()
+ company_id = vals.get('company_id', self.company_id.id)
values.update({
'pbm_loc_id': {
'name': _('Pre-Production'),
'active': manufacture_steps in ('pbm', 'pbm_sam'),
'usage': 'internal',
- 'barcode': code + '-PREPRODUCTION'
+ 'barcode': self._valid_barcode(code + '-PREPRODUCTION', company_id)
},
'sam_loc_id': {
'name': _('Post-Production'),
'active': manufacture_steps == 'pbm_sam',
'usage': 'internal',
- 'barcode': code + '-POSTPRODUCTION'
+ 'barcode': self._valid_barcode(code + '-POSTPRODUCTION', company_id)
},
})
return values
diff --git a/addons/payment_authorize/controllers/main.py b/addons/payment_authorize/controllers/main.py
index 70d641564a6..cf6877218db 100644
--- a/addons/payment_authorize/controllers/main.py
+++ b/addons/payment_authorize/controllers/main.py
@@ -55,14 +55,14 @@ class AuthorizeController(http.Controller):
except ValidationError as e:
message = e.args[0]
if isinstance(message, dict) and 'missing_fields' in message:
- msg = _("The transaction cannot be processed because some contact details are missing or invalid: ")
- message = msg + ', '.join(message['missing_fields']) + '. '
if request.env.user._is_public():
- message += _("Please sign in to complete your profile.")
+ message = _("Please sign in to complete the payment.")
# update message if portal mode = b2b
if request.env['ir.config_parameter'].sudo().get_param('auth_signup.allow_uninvited', 'False').lower() == 'false':
- message += _("If you don't have any account, please ask your salesperson to update your profile. ")
+ message += _(" If you don't have any account, ask your salesperson to grant you a portal access. ")
else:
+ msg = _("The transaction cannot be processed because some contact details are missing or invalid: ")
+ message = msg + ', '.join(message['missing_fields']) + '. '
message += _("Please complete your profile. ")
return {
diff --git a/addons/payment_payulatam/models/payment.py b/addons/payment_payulatam/models/payment.py
index df487543c45..1e65d3749c2 100644
--- a/addons/payment_payulatam/models/payment.py
+++ b/addons/payment_payulatam/models/payment.py
@@ -48,13 +48,13 @@ class PaymentAcquirerPayulatam(models.Model):
# payulatam will not allow any payment twise even if payment was failed last time.
# so, replace reference code if payment is not done or pending.
if tx.state not in ['done', 'pending']:
- tx.acquirer_reference = str(uuid.uuid4())
+ tx.reference = str(uuid.uuid4())
payulatam_values = dict(
values,
merchantId=self.payulatam_merchant_id,
accountId=self.payulatam_account_id,
description=values.get('reference'),
- referenceCode=tx.acquirer_reference,
+ referenceCode=tx.reference,
amount=values['amount'],
tax='0', # This is the transaction VAT. If VAT zero is sent the system, 19% will be applied automatically. It can contain two decimals. Eg 19000.00. In the where you do not charge VAT, it should should be set as 0.
taxReturnBase='0',
@@ -101,8 +101,8 @@ class PaymentTransactionPayulatam(models.Model):
def _payulatam_form_get_invalid_parameters(self, data):
invalid_parameters = []
- if self.acquirer_reference and data.get('referenceCode') != self.acquirer_reference:
- invalid_parameters.append(('Reference code', data.get('referenceCode'), self.acquirer_reference))
+ if self.acquirer_reference and data.get('transactionId') != self.acquirer_reference:
+ invalid_parameters.append(('Reference code', data.get('transactionId'), self.acquirer_reference))
if float_compare(float(data.get('TX_VALUE', '0.0')), self.amount, 2) != 0:
invalid_parameters.append(('Amount', data.get('TX_VALUE'), '%.2f' % self.amount))
if data.get('merchantId') != self.acquirer_id.payulatam_merchant_id:
diff --git a/addons/point_of_sale/data/point_of_sale_data.xml b/addons/point_of_sale/data/point_of_sale_data.xml
index 8fcb592f8f3..810925e61e3 100644
--- a/addons/point_of_sale/data/point_of_sale_data.xml
+++ b/addons/point_of_sale/data/point_of_sale_data.xml
@@ -1,13 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
- <data noupdate="0">
+ <data noupdate="1">
<record id="seq_picking_type_posout" model="ir.sequence">
<field name="name">Picking POS</field>
<field name="prefix">POS</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
-
+ </data>
+ <data noupdate="0">
<record id="picking_type_posout" model="stock.picking.type">
<field name="name">PoS Orders</field>
<field name="sequence_id" ref="seq_picking_type_posout"/>
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 8303f2ec7ae..ed55db56c46 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -363,7 +363,7 @@ class PosOrder(models.Model):
account_analytic=account_analytic)
if res:
line1, line2 = res
- line1 = Product._convert_prepared_anglosaxon_line(line1, order.partner_id)
+ line1 = Product._convert_prepared_anglosaxon_line(line1, line['partner_id'])
insert_data('counter_part', {
'name': line1['name'],
'account_id': line1['account_id'],
@@ -373,7 +373,7 @@ class PosOrder(models.Model):
})
- line2 = Product._convert_prepared_anglosaxon_line(line2, order.partner_id)
+ line2 = Product._convert_prepared_anglosaxon_line(line2, line['partner_id'])
insert_data('counter_part', {
'name': line2['name'],
'account_id': line2['account_id'],
@@ -400,7 +400,6 @@ class PosOrder(models.Model):
def insert_data(data_type, values):
# if have_to_group_by:
values.update({
- 'partner_id': partner_id,
'move_id': move.id,
})
@@ -755,7 +754,7 @@ class PosOrder(models.Model):
try:
pos_order.action_pos_order_paid()
- except psycopg2.OperationalError:
+ except psycopg2.DatabaseError:
# do not hide transactional errors, the order(s) won't be saved!
raise
except Exception as e:
@@ -763,7 +762,7 @@ class PosOrder(models.Model):
if to_invoice:
pos_order.action_pos_order_invoice()
- pos_order.invoice_id.sudo().action_invoice_open()
+ pos_order.invoice_id.sudo().with_context(force_company=self.env.user.company_id.id).action_invoice_open()
pos_order.account_move = pos_order.invoice_id.move_id
return order_ids
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 6797107a367..68ba406c2f0 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -2697,7 +2697,7 @@ exports.NumpadState = Backbone.Model.extend({
this.set({
buffer: "-" + newChar
});
- } else {
+ } else if (!(newChar === '.') || oldBuffer.indexOf('.') === -1) {
this.set({
buffer: (this.get('buffer')) + newChar
});
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index 1d08da927ea..78b7d2947eb 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -151,6 +151,61 @@ var ScreenWidget = PosBaseWidget.extend({
}
}
},
+ /**
+ * Handles the error response from the server when we push
+ * an invoiceable order
+ * Displays appropriates warnings and errors and
+ * proposes subsequent actions
+ *
+ * @private
+ * @param {PosModel} order: the order to consider, defaults to current order
+ * @param {Boolean} refresh_screens: whether or not displayed screens should refresh
+ * @param {Object} error: the error provided by Ajax
+ */
+ _handleFailedPushForInvoice: function (order, refresh_screen, error) {
+ var self = this;
+ order = order || this.pos.get_order();
+ this.invoicing = false;
+ order.finalized = false;
+ if (error.message === 'Missing Customer') {
+ this.gui.show_popup('confirm',{
+ 'title': _t('Please select the Customer'),
+ 'body': _t('You need to select the customer before you can invoice an order.'),
+ confirm: function(){
+ self.gui.show_screen('clientlist', null, refresh_screen);
+ },
+ });
+ } else if (error.message === 'Backend Invoice') {
+ this.gui.show_popup('confirm',{
+ 'title': _t('Please print the invoice from the backend'),
+ 'body': _t('The order has been synchronized earlier. Please make the invoice from the backend for the order: ') + error.data.order.name,
+ confirm: function () {
+ this.gui.show_screen('receipt', null, refresh_screen);
+ },
+ cancel: function () {
+ this.gui.show_screen('receipt', null, refresh_screen);
+ },
+ });
+ } else if (error.code < 0) { // XmlHttpRequest Errors
+ this.gui.show_popup('error',{
+ 'title': _t('The order could not be sent'),
+ 'body': _t('Check your internet connection and try again.'),
+ cancel: function () {
+ this.gui.show_screen('receipt', {button_print_invoice: true}, refresh_screen); // refresh if necessary
+ },
+ });
+ } else if (error.code === 200) { // OpenERP Server Errors
+ this.gui.show_popup('error-traceback',{
+ 'title': error.data.message || _t("Server Error"),
+ 'body': error.data.debug || _t('The server encountered an error while receiving your order.'),
+ });
+ } else { // ???
+ this.gui.show_popup('error',{
+ 'title': _t("Unknown Error"),
+ 'body': _t("The order could not be sent to the server due to an unknown error"),
+ });
+ }
+ },
});
/*--------------------------------------*\
@@ -1553,7 +1608,9 @@ var ReceiptScreenWidget = ScreenWidget.extend({
return this.pos.config.iface_print_auto && !this.pos.get_order()._printed;
},
should_close_immediately: function() {
- return this.pos.config.iface_print_via_proxy && this.pos.config.iface_print_skip_screen;
+ var order = this.pos.get_order();
+ var invoiced_finalized = order.is_to_invoice() ? order.finalized : true;
+ return this.pos.config.iface_print_via_proxy && this.pos.config.iface_print_skip_screen && invoiced_finalized;
},
lock_screen: function(locked) {
this._locked = locked;
@@ -1657,9 +1714,32 @@ var ReceiptScreenWidget = ScreenWidget.extend({
self.print();
}
});
+ var button_print_invoice = this.$('.button.print_invoice');
+ button_print_invoice.click(function () {
+ var order = self.pos.get_order();
+ var invoiced = self.pos.push_and_invoice_order(order);
+ self.invoicing = true;
+
+ invoiced.fail(self._handleFailedPushForInvoice.bind(self, order, true)); // refresh
+
+ invoiced.then(function(){
+ self.invoicing = false;
+ self.gui.show_screen('receipt', {button_print_invoice: false}, true); // refresh
+ });
+ });
+
},
render_change: function() {
+ var self = this;
this.$('.change-value').html(this.format_currency(this.pos.get_order().get_change()));
+ var order = this.pos.get_order();
+ var order_screen_params = order.get_screen_data('params');
+ var button_print_invoice = this.$('.button.print_invoice');
+ if (order_screen_params && order_screen_params.button_print_invoice) {
+ button_print_invoice.show();
+ } else {
+ button_print_invoice.hide();
+ }
},
render_receipt: function() {
this.$('.pos-receipt-container').html(QWeb.render('PosTicket', this.get_receipt_render_env()));
@@ -2084,45 +2164,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
var invoiced = this.pos.push_and_invoice_order(order);
this.invoicing = true;
- invoiced.catch(function (error) {
- self.invoicing = false;
- order.finalized = false;
- if (error.message === 'Missing Customer') {
- self.gui.show_popup('confirm',{
- 'title': _t('Please select the Customer'),
- 'body': _t('You need to select the customer before you can invoice an order.'),
- confirm: function(){
- self.gui.show_screen('clientlist');
- },
- });
- } else if (error.message === 'Backend Invoice') {
- self.gui.show_popup('confirm',{
- 'title': _t('Please print the invoice from the backend'),
- 'body': _t('The order has been synchronized earlier. Please make the invoice from the backend for the order: ') + error.data.order.name,
- confirm: function () {
- this.gui.show_screen('receipt');
- },
- cancel: function () {
- this.gui.show_screen('receipt');
- },
- });
- } else if (error.code < 0) { // XmlHttpRequest Errors
- self.gui.show_popup('error',{
- 'title': _t('The order could not be sent'),
- 'body': _t('Check your internet connection and try again.'),
- });
- } else if (error.code === 200) { // OpenERP Server Errors
- self.gui.show_popup('error-traceback',{
- 'title': error.data.message || _t("Server Error"),
- 'body': error.data.debug || _t('The server encountered an error while receiving your order.'),
- });
- } else { // ???
- self.gui.show_popup('error',{
- 'title': _t("Unknown Error"),
- 'body': _t("The order could not be sent to the server due to an unknown error"),
- });
- }
- });
+ invoiced.catch(this._handleFailedPushForInvoice.bind(this, order, false));
invoiced.then(function () {
self.invoicing = false;
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index b094c035b4d..26d315631a8 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -719,6 +719,9 @@
</span>
</div>
<div class="centered-content touch-scrollable">
+ <div class="button print_invoice">
+ <i class='fa fa-print'></i> Print Invoice
+ </div>
<div class="button print">
<i class='fa fa-print'></i> Print Receipt
</div>
@@ -805,7 +808,7 @@
</left>
</line>
</t>
- <t t-else='line.discount !== 0'>
+ <t t-elif='line.discount !== 0'>
<line indent='1'>
<left>
<value t-att-value-decimals='pos.dp["Product Price"]'>
diff --git a/addons/point_of_sale/views/account_journal_view.xml b/addons/point_of_sale/views/account_journal_view.xml
index 94c80cbacef..99e0403306e 100644
--- a/addons/point_of_sale/views/account_journal_view.xml
+++ b/addons/point_of_sale/views/account_journal_view.xml
@@ -5,8 +5,7 @@
<field name="res_model">account.journal</field>
<field name="view_type">form</field>
<field name="view_mode">tree,kanban,form</field>
- <field name="context" eval="{'default_journal_user': 1, 'default_type': 'cash'}"/>
- <field name="domain">[('journal_user','=', 1)]</field>
+ <field name="context" eval="{'search_default_available_in_pos': True, 'default_journal_user': True, 'default_type': 'cash'}"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Add a new payment method
diff --git a/addons/portal/controllers/mail.py b/addons/portal/controllers/mail.py
index 411106dc188..8d1378218a1 100644
--- a/addons/portal/controllers/mail.py
+++ b/addons/portal/controllers/mail.py
@@ -137,7 +137,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)], domain])
+ domain = expression.AND([['&', '&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False), ('website_published', '=', True)], domain])
Message = request.env['mail.message'].sudo()
return {
'messages': Message.search(domain, limit=limit, offset=offset).portal_message_format(),
diff --git a/addons/portal/views/portal_templates.xml b/addons/portal/views/portal_templates.xml
index 462a93a5936..f4f88c5dfb2 100644
--- a/addons/portal/views/portal_templates.xml
+++ b/addons/portal/views/portal_templates.xml
@@ -24,7 +24,7 @@
<t t-set="direction" t-value="request.env['res.lang'].search([('code', '=', request.env.lang)]).direction"/>
<div id="wrapwrap" t-attf-class="#{pageName or ''} #{direction == 'rtl' and 'o_rtl' or ''} #{'o_portal' if is_portal else ''}">
- <header id="top" data-anchor="true">
+ <header id="top" data-anchor="true" t-if="not hide_header">
<nav class="navbar navbar-expand-md navbar-light bg-light">
<div class="container">
<a href="/" class="navbar-brand logo">
@@ -55,7 +55,7 @@
<main>
<t t-raw="0"/>
</main>
- <footer id="bottom" data-anchor="true" class="bg-light o_footer">
+ <footer id="bottom" data-anchor="true" class="bg-light o_footer" t-if="not hide_footer">
<div class="container mt16 mb16" id="footer">
<div class="float-right mb16" t-ignore="true" t-if="not editable">
Powered by <a target="_blank" href="http://www.odoo.com?utm_source=db&amp;utm_medium=portal">Odoo</a>
@@ -174,7 +174,7 @@
</template>
<template id="portal_record_sidebar" name="My Portal Record Sidebar">
- <div t-attf-class="col-12 col-lg flex-lg-grow-0 #{classes}">
+ <div t-attf-class="#{classes}">
<div class="card bg-white mb-4" id="sidebar_content">
<div t-if="title" class="card-body text-center pb-2 pt-3">
<t t-raw="title"/>
diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py
index 56f9e10a840..3b1ce35701e 100644
--- a/addons/product/models/product_template.py
+++ b/addons/product/models/product_template.py
@@ -367,6 +367,9 @@ class ProductTemplate(models.Model):
related_vals['volume'] = vals['volume']
if vals.get('weight'):
related_vals['weight'] = vals['weight']
+ # Please do forward port
+ if vals.get('packaging_ids'):
+ related_vals['packaging_ids'] = vals['packaging_ids']
if related_vals:
template.write(related_vals)
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index 98a437c1c6d..6f5d1a9ecfe 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -458,7 +458,6 @@ class Task(models.Model):
date_assign = fields.Datetime(string='Assigning Date', index=True, copy=False, readonly=True)
date_deadline = fields.Date(string='Deadline', index=True, copy=False, tracking=True)
date_last_stage_update = fields.Datetime(string='Last Stage Update',
- default=fields.Datetime.now,
index=True,
copy=False,
readonly=True)
@@ -669,9 +668,10 @@ class Task(models.Model):
# user_id change: update date_assign
if vals.get('user_id'):
vals['date_assign'] = fields.Datetime.now()
- # Stage change: Update date_end if folded stage
+ # Stage change: Update date_end if folded stage and date_last_stage_update
if vals.get('stage_id'):
vals.update(self.update_date_end(vals['stage_id']))
+ vals['date_last_stage_update'] = fields.Datetime.now()
task = super(Task, self.with_context(context)).create(vals)
return task
diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py
index 6c7b1b9293d..a6b0513bd20 100644
--- a/addons/purchase/models/account_invoice.py
+++ b/addons/purchase/models/account_invoice.py
@@ -25,10 +25,6 @@ class AccountInvoice(models.Model):
return {}
self.purchase_id = self.vendor_bill_purchase_id.purchase_order_id
self.vendor_bill_id = self.vendor_bill_purchase_id.vendor_bill_id
- vendor_ref = self.vendor_bill_purchase_id.purchase_order_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
self.vendor_bill_purchase_id = False
return {}
@@ -98,9 +94,20 @@ class AccountInvoice(models.Model):
if not self.partner_id:
self.partner_id = self.purchase_id.partner_id.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
+
if not self.invoice_line_ids:
#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/report/purchase_bill.py b/addons/purchase/report/purchase_bill.py
index 036309e8e29..009ce1f6ac8 100644
--- a/addons/purchase/report/purchase_bill.py
+++ b/addons/purchase/report/purchase_bill.py
@@ -37,7 +37,7 @@ class PurchaseBillUnion(models.Model):
NULL as vendor_bill_id, id as purchase_order_id
FROM purchase_order
WHERE
- state = 'purchase' AND
+ state in ('purchase', 'done') AND
invoice_status in ('to invoice', 'no')
)""")
diff --git a/addons/purchase/views/purchase_views.xml b/addons/purchase/views/purchase_views.xml
index 29c7c730da5..981e4e60830 100644
--- a/addons/purchase/views/purchase_views.xml
+++ b/addons/purchase/views/purchase_views.xml
@@ -138,12 +138,12 @@
<button name="print_quotation" string="Print RFQ" type="object" states="draft" class="oe_highlight" groups="base.group_user"/>
<button name="button_confirm" type="object" states="sent" string="Confirm Order" class="oe_highlight" id="bid_confirm"/>
<button name="button_approve" type="object" states='to approve' string="Approve Order" class="oe_highlight" groups="purchase.group_purchase_manager"/>
- <button name="action_view_invoice" string="Create Bill" type="object" class="oe_highlight" context="{'create_bill':True}" attrs="{'invisible': ['|', ('state', 'not in', ('purchase')), ('invoice_status', 'in', ('no', 'invoiced'))]}"/>
+ <button name="action_view_invoice" string="Create Bill" type="object" class="oe_highlight" context="{'create_bill':True}" attrs="{'invisible': ['|', ('state', 'not in', ('purchase', 'done')), ('invoice_status', 'in', ('no', 'invoiced'))]}"/>
<button name="action_rfq_send" states="sent" string="Re-Send by Email" type="object" context="{'send_rfq':True}"/>
<button name="print_quotation" string="Print RFQ" type="object" states="sent" groups="base.group_user"/>
<button name="button_confirm" type="object" states="draft" string="Confirm Order" id="draft_confirm"/>
<button name="action_rfq_send" states="purchase" string="Send PO by Email" type="object" context="{'send_rfq':False}"/>
- <button name="action_view_invoice" string="Create Bill" type="object" context="{'create_bill':True}" attrs="{'invisible': ['|', '|', ('state', 'not in', ('purchase')), ('invoice_status', 'not in', ('no', 'invoiced')), ('order_line', '=', [])]}"/>
+ <button name="action_view_invoice" string="Create Bill" type="object" context="{'create_bill':True}" attrs="{'invisible': ['|', '|', ('state', 'not in', ('purchase', 'done')), ('invoice_status', 'not in', ('no', 'invoiced')), ('order_line', '=', [])]}"/>
<button name="button_draft" states="cancel" string="Set to Draft" type="object" />
<button name="button_cancel" states="draft,to approve,sent,purchase" string="Cancel" type="object" />
<button name="button_done" type="object" string="Lock" states="purchase"/>
diff --git a/addons/purchase_stock/models/account_invoice.py b/addons/purchase_stock/models/account_invoice.py
index efb494777cd..78cd9ba2707 100644
--- a/addons/purchase_stock/models/account_invoice.py
+++ b/addons/purchase_stock/models/account_invoice.py
@@ -48,7 +48,6 @@ class AccountInvoice(models.Model):
inv.currency_id,
company=inv.company_id, date=fields.Date.today(), round=False,
)
- line_quantity = line['quantity']
if i_line.product_id.cost_method != 'standard' and i_line.purchase_line_id:
po_currency = i_line.purchase_id.currency_id
@@ -75,16 +74,17 @@ class AccountInvoice(models.Model):
for val_stock_move in valuation_stock_move:
# In case val_stock_move is a return move, its valuation entries have been made with the
# currency rate corresponding to the original stock move
- valuation_date = val_stock_move.origin_returned_move_id.date or val_stock_move.date_expected
+ valuation_date = val_stock_move.origin_returned_move_id.date or val_stock_move.date
valuation_price_unit_total += company_currency._convert(
abs(val_stock_move.price_unit) * val_stock_move.product_qty,
inv.currency_id,
company=inv.company_id, date=valuation_date, round=False,
)
valuation_total_qty += val_stock_move.product_qty
+
+ # in Stock Move, price unit is in company_currency
valuation_price_unit = valuation_price_unit_total / valuation_total_qty
valuation_price_unit = i_line.product_id.uom_id._compute_price(valuation_price_unit, i_line.uom_id)
- line_quantity = valuation_total_qty
elif i_line.product_id.cost_method == 'fifo':
# In this condition, we have a real price-valuated product which has not yet been received
@@ -93,7 +93,7 @@ class AccountInvoice(models.Model):
company=po_company, date=inv.date or inv.date_invoice, round=False,
)
- interim_account_price = valuation_price_unit * line_quantity
+ 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:
@@ -124,7 +124,7 @@ class AccountInvoice(models.Model):
'type': 'src',
'name': i_line.name[:64],
'price_unit': price_unit_val_dif,
- 'quantity': line_quantity,
+ 'quantity': line['quantity'],
'price': inv.currency_id.round(price_val_dif),
'account_id': acc,
'product_id': line['product_id'],
diff --git a/addons/purchase_stock/models/stock.py b/addons/purchase_stock/models/stock.py
index 6994e1d3844..61e285db200 100644
--- a/addons/purchase_stock/models/stock.py
+++ b/addons/purchase_stock/models/stock.py
@@ -59,7 +59,13 @@ class StockMove(models.Model):
if self.purchase_line_id:
purchase_currency = self.purchase_line_id.currency_id
if purchase_currency != self.company_id.currency_id:
- purchase_price_unit = self.purchase_line_id.price_unit
+ # Do not use price_unit since we want the price tax excluded. And by the way, qty
+ # is in the UOM of the product, not the UOM of the PO line.
+ purchase_price_unit = (
+ self.purchase_line_id.price_subtotal / self.purchase_line_id.product_uom_qty
+ if self.purchase_line_id.product_uom_qty
+ else self.purchase_line_id.price_unit
+ )
currency_move_valuation = purchase_currency.round(purchase_price_unit * abs(qty))
rslt['credit_line_vals']['amount_currency'] = rslt['credit_line_vals']['credit'] and -currency_move_valuation or currency_move_valuation
rslt['credit_line_vals']['currency_id'] = purchase_currency.id
diff --git a/addons/purchase_stock/tests/test_anglo_saxon_valuation_reconciliation.py b/addons/purchase_stock/tests/test_anglo_saxon_valuation_reconciliation.py
index 93192940cd5..86cb9a64216 100644
--- a/addons/purchase_stock/tests/test_anglo_saxon_valuation_reconciliation.py
+++ b/addons/purchase_stock/tests/test_anglo_saxon_valuation_reconciliation.py
@@ -62,8 +62,10 @@ class TestValuationReconciliation(ValuationReconciliationTestCase):
""" Tests the case into which we receive the goods first, and then make the invoice.
"""
test_product = self.test_product_delivery
- purchase_order = self._create_purchase(test_product, '2018-01-01')
- self._process_pickings(purchase_order.picking_ids)
+ date_po_and_delivery = '2018-01-01'
+
+ purchase_order = self._create_purchase(test_product, date_po_and_delivery)
+ self._process_pickings(purchase_order.picking_ids, date=date_po_and_delivery)
invoice = self._create_invoice_for_po(purchase_order, '2018-02-02')
self.env['res.currency.rate'].create({
@@ -151,8 +153,9 @@ class TestValuationReconciliation(ValuationReconciliationTestCase):
""" Tests the case into which we receive part of the goods first, then 2 invoices at different rates, and finally the remaining quantities
"""
test_product = self.test_product_delivery
- purchase_order = self._create_purchase(test_product, '2017-01-01', quantity=5.0)
- self._process_pickings(purchase_order.picking_ids, quantity=2.0)
+ date_po_and_delivery0 = '2017-01-01'
+ purchase_order = self._create_purchase(test_product, date_po_and_delivery0, quantity=5.0)
+ self._process_pickings(purchase_order.picking_ids, quantity=2.0, date=date_po_and_delivery0)
picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)], order="id asc", limit=1)
invoice = self._create_invoice_for_po(purchase_order, '2017-01-15')
@@ -185,6 +188,8 @@ class TestValuationReconciliation(ValuationReconciliationTestCase):
'rate': 12.195747002,
'name': '2017-04-01',
})
+ # We don't need to make the date of processing explicit since the very last rate
+ # will be taken
self._process_pickings(purchase_order.picking_ids.filtered(lambda x: x.state != 'done'), quantity=3.0)
picking = self.env['stock.picking'].search([('purchase_id', '=', purchase_order.id)], order='id desc', limit=1)
self.check_reconciliation(invoice2, picking)
diff --git a/addons/purchase_stock/tests/test_stockvaluation.py b/addons/purchase_stock/tests/test_stockvaluation.py
index a72c4d4b633..4b034316eac 100644
--- a/addons/purchase_stock/tests/test_stockvaluation.py
+++ b/addons/purchase_stock/tests/test_stockvaluation.py
@@ -3,6 +3,7 @@
import time
from datetime import datetime
+from unittest.mock import patch
from odoo import fields
from odoo.tests import Form
@@ -144,6 +145,7 @@ class TestStockValuation(TransactionCase):
the currency rate, validate the receipt and then check that the value of the received goods
is set according to the last currency rate.
"""
+ self.env['res.currency.rate'].search([]).unlink()
usd_currency = self.env.ref('base.USD')
self.env.user.company_id.currency_id = usd_currency.id
@@ -290,6 +292,9 @@ class TestStockValuationWithCOA(AccountingTestCase):
self.partner_id = self.env.ref('base.res_partner_1')
self.product1 = self.env.ref('product.product_product_8')
Account = self.env['account.account']
+ self.usd_currency = self.env.ref('base.USD')
+ self.eur_currency = self.env.ref('base.EUR')
+
self.stock_input_account = Account.create({
'name': 'Stock Input',
'code': 'StockIn',
@@ -307,6 +312,11 @@ class TestStockValuationWithCOA(AccountingTestCase):
'code': 'Stock Valuation',
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
})
+ self.price_diff_account = Account.create({
+ 'name': 'price diff account',
+ 'code': 'price diff account',
+ 'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
+ })
self.stock_journal = self.env['account.journal'].create({
'name': 'Stock Journal',
'code': 'STJTEST',
@@ -324,12 +334,7 @@ class TestStockValuationWithCOA(AccountingTestCase):
self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
self.product1.product_tmpl_id.invoice_policy = 'delivery'
- price_diff_account = self.env['account.account'].create({
- 'name': 'price diff account',
- 'code': 'price diff account',
- 'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
- })
- self.product1.property_account_creditor_price_difference = price_diff_account
+ self.product1.property_account_creditor_price_difference = self.price_diff_account
# Receive 10@10 ; create the vendor bill
po1 = self.env['purchase.order'].create({
@@ -417,7 +422,7 @@ class TestStockValuationWithCOA(AccountingTestCase):
creditnote_po2.action_invoice_open()
# check the anglo saxon entries
- price_diff_entry = self.env['account.move.line'].search([('account_id', '=', price_diff_account.id)])
+ price_diff_entry = self.env['account.move.line'].search([('account_id', '=', self.price_diff_account.id)])
self.assertEqual(price_diff_entry.credit, 100)
def test_anglosaxon_valuation(self):
@@ -425,12 +430,7 @@ class TestStockValuationWithCOA(AccountingTestCase):
self.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
self.product1.product_tmpl_id.invoice_policy = 'delivery'
- price_diff_account = self.env['account.account'].create({
- 'name': 'price diff account',
- 'code': 'price diff account',
- 'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
- })
- self.product1.property_account_creditor_price_difference = price_diff_account
+ self.product1.property_account_creditor_price_difference = self.price_diff_account
# Create PO
po_form = Form(self.env['purchase.order'])
@@ -459,12 +459,12 @@ class TestStockValuationWithCOA(AccountingTestCase):
invoice.action_invoice_open()
# Check what was posted in the price difference account
- price_diff_aml = self.env['account.move.line'].search([('account_id','=',price_diff_account.id)])
+ 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.debit, 5, "Price difference should be equal to 5 (15-10)")
# Check what was posted in stock input account
- input_aml = self.env['account.move.line'].search([('account_id','=',self.stock_input_account.id)])
+ 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')), 10, "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')), 10, "Total credit value on stock input account should be equal to the original PO price of the product.")
@@ -507,3 +507,661 @@ class TestStockValuationWithCOA(AccountingTestCase):
# valuation of product1 should be 15 as the tax with no account set
# has gone to the stock account, and must be reflected in inventory valuation
self.assertEqual(self.product1.stock_value, 150)
+ def test_average_realtime_anglo_saxon_valuation_multicurrency_same_date(self):
+ """
+ The PO and invoice are in the same foreign currency.
+ The PO is invoiced on the same date as its creation.
+ This shouldn't create a price difference entry.
+ """
+ company = self.env.user.company_id
+ company.anglo_saxon_accounting = True
+
+ date_po = '2019-01-01'
+
+ # SetUp product
+ self.product1.product_tmpl_id.cost_method = 'average'
+ self.product1.product_tmpl_id.valuation = 'real_time'
+ self.product1.product_tmpl_id.invoice_policy = 'order'
+ self.product1.product_tmpl_id.purchase_method = 'purchase'
+
+ self.product1.property_account_creditor_price_difference = self.price_diff_account
+
+ # SetUp currency and rates
+ self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
+ self.env['res.currency.rate'].search([]).unlink()
+ self.env['res.currency.rate'].create({
+ 'name': date_po,
+ 'rate': 1.0,
+ 'currency_id': self.usd_currency.id,
+ 'company_id': company.id,
+ })
+
+ self.env['res.currency.rate'].create({
+ 'name': date_po,
+ 'rate': 1.5,
+ 'currency_id': self.eur_currency.id,
+ 'company_id': company.id,
+ })
+
+ # Proceed
+ po = self.env['purchase.order'].create({
+ 'currency_id': self.eur_currency.id,
+ 'partner_id': self.partner_id.id,
+ 'order_line': [
+ (0, 0, {
+ 'name': self.product1.name,
+ 'product_id': self.product1.id,
+ 'product_qty': 1.0,
+ 'product_uom': self.product1.uom_po_id.id,
+ 'price_unit': 100.0,
+ 'date_planned': date_po,
+ }),
+ ],
+ })
+ po.button_confirm()
+
+ inv = self.env['account.invoice'].create({
+ 'type': 'in_invoice',
+ 'date_invoice': date_po,
+ 'currency_id': self.eur_currency.id,
+ 'purchase_id': po.id,
+ 'partner_id': self.partner_id.id,
+ 'invoice_line_ids': [(0, 0, {
+ 'name': 'Test',
+ 'price_subtotal': 100.0,
+ 'price_unit': 100.0,
+ 'product_id': self.product1.id,
+ 'purchase_id': po.id,
+ 'purchase_line_id': po.order_line.id,
+ 'quantity': 1.0,
+ 'account_id': self.stock_input_account.id,
+ })]
+ })
+
+ inv.action_invoice_open()
+
+ move_lines = inv.move_id.line_ids
+ self.assertEqual(len(move_lines), 2)
+
+ payable_line = move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
+ self.assertEqual(payable_line.amount_currency, -100.0)
+ self.assertAlmostEqual(payable_line.balance, -66.67)
+
+ stock_line = move_lines.filtered(lambda l: l.account_id == self.stock_input_account)
+ self.assertEqual(stock_line.amount_currency, 100.0)
+ self.assertAlmostEqual(stock_line.balance, 66.67)
+
+ def test_realtime_anglo_saxon_valuation_multicurrency_different_dates(self):
+ """
+ The PO and invoice are in the same foreign currency.
+ The PO is invoiced at a later date than its creation.
+ This should create a price difference entry for standard cost method
+ Not for average cost method though, since the PO and invoice have the same currency
+ """
+ company = self.env.user.company_id
+ company.anglo_saxon_accounting = True
+ self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
+ self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
+
+ date_po = '2019-01-01'
+ date_invoice = '2019-01-16'
+
+ # SetUp product Average
+ self.product1.product_tmpl_id.write({
+ 'purchase_method': 'purchase',
+ 'property_account_creditor_price_difference': self.price_diff_account.id,
+ })
+ self.product1.invoice_policy = 'order'
+
+ # SetUp product Standard
+ # should have bought at 60 USD
+ # actually invoiced at 70 EUR > 35 USD
+ product_categ_standard = self.product1.product_tmpl_id.categ_id.copy({
+ 'property_cost_method': 'standard',
+ 'property_stock_account_input_categ_id': self.stock_input_account.id,
+ 'property_stock_account_output_categ_id': self.stock_output_account.id,
+ 'property_stock_valuation_account_id': self.stock_valuation_account.id,
+ 'property_stock_journal': self.stock_journal.id,
+ })
+ product_standard = self.product1.product_tmpl_id.copy({
+ 'categ_id': product_categ_standard.id,
+ 'name': 'Standard Val',
+ 'standard_price': 60,
+ 'property_account_creditor_price_difference': self.price_diff_account.id
+ }).product_variant_id
+
+ # SetUp currency and rates
+ self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
+ self.env['res.currency.rate'].search([]).unlink()
+ self.env['res.currency.rate'].create({
+ 'name': date_po,
+ 'rate': 1.0,
+ 'currency_id': self.usd_currency.id,
+ 'company_id': company.id,
+ })
+
+ self.env['res.currency.rate'].create({
+ 'name': date_po,
+ 'rate': 1.5,
+ 'currency_id': self.eur_currency.id,
+ 'company_id': company.id,
+ })
+
+ self.env['res.currency.rate'].create({
+ 'name': date_invoice,
+ 'rate': 2,
+ 'currency_id': self.eur_currency.id,
+ 'company_id': company.id,
+ })
+
+ # To allow testing validation of PO
+ def _today(*args, **kwargs):
+ return date_po
+ patchers = [
+ patch('odoo.fields.Date.context_today', _today),
+ ]
+
+ for p in patchers:
+ p.start()
+
+ # Proceed
+ po = self.env['purchase.order'].create({
+ 'currency_id': self.eur_currency.id,
+ 'partner_id': self.partner_id.id,
+ 'order_line': [
+ (0, 0, {
+ 'name': self.product1.name,
+ 'product_id': self.product1.id,
+ 'product_qty': 1.0,
+ 'product_uom': self.product1.uom_po_id.id,
+ 'price_unit': 100.0,
+ 'date_planned': date_po,
+ }),
+ (0, 0, {
+ 'name': product_standard.name,
+ 'product_id': product_standard.id,
+ 'product_qty': 1.0,
+ 'product_uom': product_standard.uom_po_id.id,
+ 'price_unit': 40.0,
+ 'date_planned': date_po,
+ }),
+ ],
+ })
+ po.button_confirm()
+
+ line_product_average = po.order_line.filtered(lambda l: l.product_id == self.product1)
+ line_product_standard = po.order_line.filtered(lambda l: l.product_id == product_standard)
+
+ inv = self.env['account.invoice'].create({
+ 'type': 'in_invoice',
+ 'date_invoice': date_invoice,
+ 'currency_id': self.eur_currency.id,
+ 'purchase_id': po.id,
+ 'partner_id': self.partner_id.id,
+ 'invoice_line_ids': [
+ (0, 0, {
+ 'name': self.product1.name,
+ 'price_subtotal': 100.0,
+ 'price_unit': 100.0,
+ 'product_id': self.product1.id,
+ 'purchase_id': po.id,
+ 'purchase_line_id': line_product_average.id,
+ 'quantity': 1.0,
+ 'account_id': self.stock_input_account.id,
+ }),
+ (0, 0, {
+ 'name': product_standard.name,
+ 'price_subtotal': 70.0,
+ 'price_unit': 70.0,
+ 'product_id': product_standard.id,
+ 'purchase_id': po.id,
+ 'purchase_line_id': line_product_standard.id,
+ 'quantity': 1.0,
+ 'account_id': self.stock_input_account.id,
+ })
+ ]
+ })
+
+ inv.action_invoice_open()
+
+ for p in patchers:
+ p.stop()
+
+ move_lines = inv.move_id.line_ids
+ self.assertEqual(len(move_lines), 4)
+
+ # Ensure no exchange difference move has been created
+ self.assertTrue(all([not l.reconciled for l in move_lines]))
+
+ # PAYABLE CHECK
+ payable_line = move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
+ self.assertEqual(payable_line.amount_currency, -170.0)
+ self.assertAlmostEqual(payable_line.balance, -85.00)
+
+ # PRODUCTS CHECKS
+
+ # NO EXCHANGE DIFFERENCE (average)
+ # We ordered for a value of 100 EUR
+ # But by the time we are invoiced for it
+ # the foreign currency appreciated from 1.5 to 2.0
+ # We still have to pay 100 EUR, which now values at 50 USD
+ product_lines = move_lines.filtered(lambda l: l.product_id == self.product1)
+
+ # Stock-wise, we have been invoiced 100 EUR, and we ordered 100 EUR
+ # there is no price difference
+ # However, 100 EUR should be converted at the time of the invoice
+ stock_line = product_lines.filtered(lambda l: l.account_id == self.stock_input_account)
+ self.assertEqual(stock_line.amount_currency, 100.00)
+ self.assertAlmostEqual(stock_line.balance, 50.00)
+
+ # PRICE DIFFERENCE (STANDARD)
+ # We ordered a product that should have cost 60 USD (120 EUR)
+ # However, we effectively got invoiced 70 EUR (35 USD)
+ product_lines = move_lines.filtered(lambda l: l.product_id == product_standard)
+
+ stock_line = product_lines.filtered(lambda l: l.account_id == self.stock_input_account)
+ self.assertEqual(stock_line.amount_currency, 120.00)
+ self.assertAlmostEqual(stock_line.balance, 60.00)
+
+ price_diff_line = product_lines.filtered(lambda l: l.account_id == self.price_diff_account)
+ self.assertEqual(price_diff_line.amount_currency, -50.00)
+ self.assertAlmostEqual(price_diff_line.balance, -25.00)
+
+ def test_average_realtime_with_delivery_anglo_saxon_valuation_multicurrency_different_dates(self):
+ """
+ The PO and invoice are in the same foreign currency.
+ The delivery occurs in between PO validation and invoicing
+ The invoice is created at an even different date
+ This should create a price difference entry.
+ """
+ company = self.env.user.company_id
+ company.anglo_saxon_accounting = True
+ self.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
+ self.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
+
+ date_po = '2019-01-01'
+ date_delivery = '2019-01-08'
+ date_invoice = '2019-01-16'
+
+ product_avg = self.product1.product_tmpl_id.copy({
+ 'purchase_method': 'purchase',
+ 'name': 'AVG',
+ 'standard_price': 60,
+ 'property_account_creditor_price_difference': self.price_diff_account.id
+ }).product_variant_id
+ product_avg.invoice_policy = 'order'
+
+ # SetUp currency and rates
+ self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
+ self.env['res.currency.rate'].search([]).unlink()
+ self.env['res.currency.rate'].create({
+ 'name': date_po,
+ 'rate': 1.0,
+ 'currency_id': self.usd_currency.id,
+ 'company_id': company.id,
+ })
+
+ self.env['res.currency.rate'].create({
+ 'name': date_po,
+ 'rate': 1.5,
+ 'currency_id': self.eur_currency.id,
+ 'company_id': company.id,
+ })
+
+ self.env['res.currency.rate'].create({
+ 'name': date_delivery,
+ 'rate': 0.7,
+ 'currency_id': self.eur_currency.id,
+ 'company_id': company.id,
+ })
+
+ self.env['res.currency.rate'].create({
+ 'name': date_invoice,
+ 'rate': 2,
+ 'currency_id': self.eur_currency.id,
+ 'company_id': company.id,
+ })
+
+ # To allow testing validation of PO
+ def _today(*args, **kwargs):
+ return date_po
+ # To allow testing validation of Delivery
+ def _now(*args, **kwargs):
+ return date_delivery + ' 01:00:00'
+
+ patchers = [
+ patch('odoo.fields.Date.context_today', _today),
+ patch('odoo.fields.Datetime.now', _now),
+ ]
+
+ for p in patchers:
+ p.start()
+
+ # Proceed
+ po = self.env['purchase.order'].create({
+ 'currency_id': self.eur_currency.id,
+ 'partner_id': self.partner_id.id,
+ 'order_line': [
+ (0, 0, {
+ 'name': product_avg.name,
+ 'product_id': product_avg.id,
+ 'product_qty': 1.0,
+ 'product_uom': product_avg.uom_po_id.id,
+ 'price_unit': 30.0,
+ 'date_planned': date_po,
+ })
+ ],
+ })
+ po.button_confirm()
+
+ line_product_avg = po.order_line.filtered(lambda l: l.product_id == product_avg)
+
+ picking = po.picking_ids
+ (picking.move_lines
+ .filtered(lambda l: l.purchase_line_id == line_product_avg)
+ .write({'quantity_done': 1.0}))
+
+ picking.button_validate()
+
+ inv = self.env['account.invoice'].create({
+ 'type': 'in_invoice',
+ 'date_invoice': date_invoice,
+ 'currency_id': self.eur_currency.id,
+ 'purchase_id': po.id,
+ 'partner_id': self.partner_id.id,
+ 'invoice_line_ids': [
+ (0, 0, {
+ 'name': product_avg.name,
+ 'price_subtotal': 30.0,
+ 'price_unit': 30.0,
+ 'product_id': product_avg.id,
+ 'purchase_id': po.id,
+ 'purchase_line_id': line_product_avg.id,
+ 'quantity': 1.0,
+ 'account_id': self.stock_input_account.id,
+ })
+ ]
+ })
+
+ inv.action_invoice_open()
+
+ for p in patchers:
+ p.stop()
+
+ move_lines = inv.move_id.line_ids
+ self.assertEqual(len(move_lines), 2)
+
+ # PAYABLE CHECK
+ payable_line = move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
+ self.assertEqual(payable_line.amount_currency, -30.0)
+ self.assertAlmostEqual(payable_line.balance, -15.00)
+
+ # PRODUCTS CHECKS
+
+ # DELIVERY DIFFERENCE (AVERAGE)
+ # We ordered a product at 30 EUR valued at 20 USD
+ # We received it when the exchange rate has appreciated
+ # So, the actualized 20 USD are now 20*1.5/0.7 = 42.86 USD
+ product_lines = move_lines.filtered(lambda l: l.product_id == product_avg)
+
+ # Although those 42.86 USD are just due to the exchange difference
+ stock_line = product_lines.filtered(lambda l: l.account_id == self.stock_input_account)
+ self.assertEqual(stock_line.journal_id, inv.journal_id)
+ self.assertEqual(stock_line.amount_currency, 30.00)
+ self.assertAlmostEqual(stock_line.balance, 15.00)
+ full_reconcile = stock_line.full_reconcile_id
+ self.assertTrue(full_reconcile.exists())
+
+ reconciled_lines = full_reconcile.reconciled_line_ids - stock_line
+ self.assertEqual(len(reconciled_lines), 2)
+
+ stock_journal_line = reconciled_lines.filtered(lambda l: l.journal_id == self.stock_journal)
+ self.assertEqual(stock_journal_line.amount_currency, -30.00)
+ self.assertAlmostEqual(stock_journal_line.balance, -42.86)
+
+ exhange_diff_journal = company.currency_exchange_journal_id.exists()
+ 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, 27.86)
+
+ def test_average_realtime_with_two_delivery_anglo_saxon_valuation_multicurrency_different_dates(self):
+ """
+ The PO and invoice are in the same foreign currency.
+ The deliveries occur at different times and rates
+ The invoice is created at an even different date
+ This should create a price difference entry.
+ """
+ company = self.env.user.company_id
+ company.anglo_saxon_accounting = True
+
+ date_po = '2019-01-01'
+ date_delivery = '2019-01-08'
+ date_delivery1 = '2019-01-10'
+ date_invoice = '2019-01-16'
+ date_invoice1 = '2019-01-20'
+
+ self.product1.categ_id.property_valuation = 'real_time'
+ self.product1.categ_id.property_cost_method = 'average'
+ product_avg = self.product1.product_tmpl_id.copy({
+ 'purchase_method': 'purchase',
+ 'name': 'AVG',
+ 'standard_price': 60,
+ 'property_account_creditor_price_difference': self.price_diff_account.id
+ }).product_variant_id
+ product_avg.invoice_policy = 'order'
+
+ # SetUp currency and rates
+ self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (self.usd_currency.id, company.id))
+ self.env['res.currency.rate'].search([]).unlink()
+ self.env['res.currency.rate'].create({
+ 'name': date_po,
+ 'rate': 1.0,
+ 'currency_id': self.usd_currency.id,
+ 'company_id': company.id,
+ })
+ self.env['res.currency.rate'].create({
+ 'name': date_po,
+ 'rate': 1.5,
+ 'currency_id': self.eur_currency.id,
+ 'company_id': company.id,
+ })
+
+ self.env['res.currency.rate'].create({
+ 'name': date_delivery,
+ 'rate': 0.7,
+ 'currency_id': self.eur_currency.id,
+ 'company_id': company.id,
+ })
+ self.env['res.currency.rate'].create({
+ 'name': date_delivery1,
+ 'rate': 0.8,
+ 'currency_id': self.eur_currency.id,
+ 'company_id': company.id,
+ })
+
+ self.env['res.currency.rate'].create({
+ 'name': date_invoice,
+ 'rate': 2,
+ 'currency_id': self.eur_currency.id,
+ 'company_id': company.id,
+ })
+ self.env['res.currency.rate'].create({
+ 'name': date_invoice1,
+ 'rate': 2.2,
+ 'currency_id': self.eur_currency.id,
+ 'company_id': company.id,
+ })
+
+ # To allow testing validation of PO
+ def _today(*args, **kwargs):
+ return date_po
+ # To allow testing validation of Delivery
+ delivery_now = date_delivery
+ def _now(*args, **kwargs):
+ return delivery_now + ' 01:00:00'
+
+ patchers = [
+ patch('odoo.fields.Date.context_today', _today),
+ patch('odoo.fields.Datetime.now', _now),
+ ]
+
+ for p in patchers:
+ p.start()
+
+ # Proceed
+ po = self.env['purchase.order'].create({
+ 'currency_id': self.eur_currency.id,
+ 'partner_id': self.partner_id.id,
+ 'date_order': date_po,
+ 'order_line': [
+ (0, 0, {
+ 'name': product_avg.name,
+ 'product_id': product_avg.id,
+ 'product_qty': 10.0,
+ 'product_uom': product_avg.uom_po_id.id,
+ 'price_unit': 30.0,
+ 'date_planned': date_po,
+ })
+ ],
+ })
+ po.button_confirm()
+
+ line_product_avg = po.order_line.filtered(lambda l: l.product_id == product_avg)
+
+ picking = po.picking_ids
+ (picking.move_lines
+ .filtered(lambda l: l.purchase_line_id == line_product_avg)
+ .write({'quantity_done': 5.0}))
+
+ picking.button_validate()
+ picking.action_done() # Create Backorder
+
+ inv = self.env['account.invoice'].create({
+ 'type': 'in_invoice',
+ 'date_invoice': date_invoice,
+ 'currency_id': self.eur_currency.id,
+ 'purchase_id': po.id,
+ 'partner_id': self.partner_id.id,
+ 'invoice_line_ids': [
+ (0, 0, {
+ 'name': product_avg.name,
+ 'price_subtotal': 100.0,
+ 'price_unit': 20.0,
+ 'product_id': product_avg.id,
+ 'purchase_id': po.id,
+ 'purchase_line_id': line_product_avg.id,
+ 'quantity': 5.0,
+ 'account_id': self.stock_input_account.id,
+ })
+ ]
+ })
+
+ inv.action_invoice_open()
+
+ backorder_picking = self.env['stock.picking'].search([('backorder_id', '=', picking.id)])
+ delivery_now = date_delivery1
+ (backorder_picking.move_lines
+ .filtered(lambda l: l.purchase_line_id == line_product_avg)
+ .write({'quantity_done': 5.0}))
+ backorder_picking.button_validate()
+
+ inv1 = self.env['account.invoice'].create({
+ 'type': 'in_invoice',
+ 'date_invoice': date_invoice1,
+ 'currency_id': self.eur_currency.id,
+ 'purchase_id': po.id,
+ 'partner_id': self.partner_id.id,
+ 'invoice_line_ids': [
+ (0, 0, {
+ 'name': product_avg.name,
+ 'price_subtotal': 200.0,
+ 'price_unit': 40.0,
+ 'product_id': product_avg.id,
+ 'purchase_id': po.id,
+ 'purchase_line_id': line_product_avg.id,
+ 'quantity': 5.0,
+ 'account_id': self.stock_input_account.id,
+ })
+ ]
+ })
+
+ inv1.action_invoice_open()
+
+ for p in patchers:
+ p.stop()
+
+ ##########################
+ # Invoice 0 #
+ ##########################
+ move_lines = inv.move_id.line_ids
+ self.assertEqual(len(move_lines), 3)
+
+ # PAYABLE CHECK
+ payable_line = move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
+ self.assertEqual(payable_line.amount_currency, -100.0)
+ self.assertAlmostEqual(payable_line.balance, -50.00)
+
+ # # PRODUCTS CHECKS
+
+ # DELIVERY DIFFERENCE (AVERAGE)
+ stock_line = move_lines.filtered(lambda l: l.account_id == self.stock_input_account)
+ self.assertEqual(stock_line.journal_id, inv.journal_id)
+ self.assertEqual(stock_line.amount_currency, 150.00)
+ self.assertAlmostEqual(stock_line.balance, 75.00)
+
+ price_diff_line = move_lines.filtered(lambda l: l.account_id == self.price_diff_account)
+ self.assertEqual(price_diff_line.amount_currency, -50.00)
+ self.assertAlmostEqual(price_diff_line.balance, -25.00)
+
+ full_reconcile = stock_line.full_reconcile_id
+ self.assertTrue(full_reconcile.exists())
+
+ reconciled_lines = full_reconcile.reconciled_line_ids - stock_line
+ self.assertEqual(len(reconciled_lines), 2)
+
+ stock_journal_line = reconciled_lines.filtered(lambda l: l.journal_id == self.stock_journal)
+ self.assertEqual(stock_journal_line.amount_currency, -150)
+ self.assertAlmostEqual(stock_journal_line.balance, -214.29)
+
+ exhange_diff_journal = company.currency_exchange_journal_id.exists()
+ 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, 139.29)
+
+ ##########################
+ # Invoice 1 #
+ ##########################
+ move_lines = inv1.move_id.line_ids
+ self.assertEqual(len(move_lines), 3)
+
+ # PAYABLE CHECK
+ payable_line = move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
+ self.assertEqual(payable_line.amount_currency, -200.0)
+ self.assertAlmostEqual(payable_line.balance, -90.91)
+
+ # # PRODUCTS CHECKS
+
+ # DELIVERY DIFFERENCE (AVERAGE)
+ stock_line = move_lines.filtered(lambda l: l.account_id == self.stock_input_account)
+ self.assertEqual(stock_line.journal_id, inv.journal_id)
+ self.assertEqual(stock_line.amount_currency, 150.00)
+ self.assertAlmostEqual(stock_line.balance, 68.18)
+
+ price_diff_line = move_lines.filtered(lambda l: l.account_id == self.price_diff_account)
+ self.assertEqual(price_diff_line.amount_currency, 50.00)
+ self.assertAlmostEqual(price_diff_line.balance, 22.73)
+
+ full_reconcile = stock_line.full_reconcile_id
+ self.assertTrue(full_reconcile.exists())
+
+ reconciled_lines = full_reconcile.reconciled_line_ids - stock_line
+ self.assertEqual(len(reconciled_lines), 2)
+
+ stock_journal_line = reconciled_lines.filtered(lambda l: l.journal_id == self.stock_journal)
+ self.assertEqual(stock_journal_line.amount_currency, -150)
+ self.assertAlmostEqual(stock_journal_line.balance, -187.5)
+
+ exhange_diff_journal = company.currency_exchange_journal_id.exists()
+ 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)
diff --git a/addons/resource/security/resource_security.xml b/addons/resource/security/resource_security.xml
index e26ed21379a..85c628faa47 100644
--- a/addons/resource/security/resource_security.xml
+++ b/addons/resource/security/resource_security.xml
@@ -19,5 +19,13 @@
<field name="domain_force">[('resource_id.user_id', '=', user.id)]</field>
<field name="perm_read" eval="False"/>
</record>
+
+ <record model="ir.rule" id="resource_calendar_leaves_rule_group_admin_modify">
+ <field name="name">resource.calendar.leaves: admin modifies global</field>
+ <field name="model_id" ref="model_resource_calendar_leaves"/>
+ <field name="groups" eval="[(4, ref('base.group_erp_manager'))]"/>
+ <field name="domain_force">[('resource_id', '=', False)]</field>
+ <field name="perm_read" eval="False"/>
+ </record>
</data>
</odoo>
diff --git a/addons/sale/__manifest__.py b/addons/sale/__manifest__.py
index c275df0ac9b..76e9de98159 100644
--- a/addons/sale/__manifest__.py
+++ b/addons/sale/__manifest__.py
@@ -41,7 +41,6 @@ This module contains all the common features of Sales Management and eCommerce.
'qweb': [
"static/src/xml/account_reconciliation.xml",
],
- 'uninstall_hook': "uninstall_hook",
'installable': True,
'auto_install': False
}
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 427c9a7986e..044e4600af1 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -571,7 +571,9 @@ class SaleOrder(models.Model):
invoice.compute_taxes()
# Idem for partner
so_payment_term_id = invoice.payment_term_id.id
+ fp_invoice = invoice.fiscal_position_id
invoice._onchange_partner_id()
+ invoice.fiscal_position_id = fp_invoice
# To keep the payment terms set on the SO
invoice.payment_term_id = so_payment_term_id
invoice.message_post_with_view('mail.message_origin_link',
@@ -650,10 +652,11 @@ class SaleOrder(models.Model):
@api.multi
def action_done(self):
- tx = self.sudo().transaction_ids.get_last_transaction()
- if tx and tx.state == 'pending' and tx.acquirer_id.provider == 'transfer':
- tx._set_transaction_done()
- tx.write({'is_processed': True})
+ for order in self:
+ tx = order.sudo().transaction_ids.get_last_transaction()
+ if tx and tx.state == 'pending' and tx.acquirer_id.provider == 'transfer':
+ tx._set_transaction_done()
+ tx.write({'is_processed': True})
return self.write({'state': 'done'})
@api.multi
@@ -1098,7 +1101,7 @@ class SaleOrderLine(models.Model):
result = super(SaleOrderLine, self).write(values)
return result
- order_id = fields.Many2one('sale.order', string='Order Reference', required=True, ondelete='cascade', index=True, copy=False, readonly=True)
+ order_id = fields.Many2one('sale.order', string='Order Reference', required=True, ondelete='cascade', index=True, copy=False)
name = fields.Text(string='Description', required=True)
sequence = fields.Integer(string='Sequence', default=10)
@@ -1558,7 +1561,7 @@ class SaleOrderLine(models.Model):
if currency_id.id == product_currency.id:
cur_factor = 1.0
else:
- cur_factor = currency_id._get_conversion_rate(product_currency, currency_id, self.company_id, self.order_id.date_order)
+ cur_factor = currency_id._get_conversion_rate(product_currency, currency_id, self.company_id or self.env.user.company_id, self.order_id.date_order or fields.Date.today())
product_uom = self.env.context.get('uom') or product.uom_id.id
if uom and uom.id != product_uom:
diff --git a/addons/sale/views/sale_portal_templates.xml b/addons/sale/views/sale_portal_templates.xml
index a69c8ba21cc..036b285f9fa 100644
--- a/addons/sale/views/sale_portal_templates.xml
+++ b/addons/sale/views/sale_portal_templates.xml
@@ -124,10 +124,10 @@
<div class="row mt16 o_portal_sale_sidebar">
<!-- Sidebar -->
<t t-call="portal.portal_record_sidebar">
- <t t-set="classes" t-value="'col-12 col-lg flex-lg-grow-0 d-print-none'"/>
+ <t t-set="classes" t-value="'col-lg-auto d-print-none'"/>
<t t-set="title">
- <h2 class="mb-0"><b t-field="sale_order.amount_total"/> </h2>
+ <h2 class="mb-0"><b t-field="sale_order.amount_total" data-id="total_amount"/> </h2>
</t>
<t t-set="entries">
<ul class="list-group list-group-flush flex-wrap flex-row flex-lg-column">
@@ -167,14 +167,15 @@
<b t-field="sale_order.amount_undiscounted"
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
style="text-decoration: line-through"
- class="d-block mt-1"/>
+ class="d-block mt-1"
+ data-id="amount_undiscounted" />
</small>
<t t-if="sale_order.amount_untaxed == sale_order.amount_total">
- <h4 t-field="sale_order.amount_total" class="text-success"/>
+ <h4 t-field="sale_order.amount_total" class="text-success" data-id="total_amount"/>
</t>
<t t-else="">
- <h4 t-field="sale_order.amount_untaxed" class="text-success mb-0"/>
- <small>(<span t-field="sale_order.amount_total"/> Incl. tax)</small>
+ <h4 t-field="sale_order.amount_untaxed" class="text-success mb-0" data-id="total_untaxed"/>
+ <small>(<span t-field="sale_order.amount_total" data-id="total_amount"/> Incl. tax)</small>
</t>
</li>
</t>
@@ -374,7 +375,7 @@
</t>
</div>
<div class="mb-3">
- <strong>Date:</strong> <span t-field="sale_order.create_date" t-options='{"widget": "date"}'/>
+ <strong>Date:</strong> <span t-field="sale_order.date_order" t-options='{"widget": "date"}'/>
</div>
<div class="mb-3" t-if="sale_order.validity_date">
<strong>Expiration Date:</strong> <span t-field="sale_order.validity_date" t-options='{"widget": "date"}'/>
@@ -480,8 +481,8 @@
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_id))"/>
</td>
<td class="text-right">
- <span t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
- <span t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
+ <span class="oe_order_line_price_subtotal" t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
+ <span class="oe_order_line_price_total" t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
</td>
</t>
<t t-if="line.display_type == 'line_section'">
@@ -515,12 +516,13 @@
<div id="total" class="row" name="total" style="page-break-inside: avoid;">
<div t-attf-class="#{'col-4' if report_type != 'html' else 'col-sm-7 col-md-5'} ml-auto">
+ <!-- Should be replaced in master by t-call="sale.sale_order_portal_content_totals_table" -->
<table class="table table-sm">
<tr class="border-black" style="border-bottom:1px solid #dddddd;">
<td><strong>Subtotal</strong></td>
<td class="text-right">
<span
- data-id="total_amount"
+ data-id="total_untaxed"
t-field="sale_order.amount_untaxed"
t-options='{"widget": "monetary","display_currency": sale_order.pricelist_id.currency_id}'
/>
@@ -578,4 +580,47 @@
</div>
</template>
+ <template id="sale_order_portal_content_totals_table">
+ <table class="table table-sm">
+ <tr class="border-black">
+ <td><strong>Subtotal</strong></td>
+ <td class="text-right">
+ <span
+ data-id="total_untaxed"
+ t-field="sale_order.amount_untaxed"
+ t-options='{"widget": "monetary","display_currency": sale_order.pricelist_id.currency_id}'
+ />
+ </td>
+ </tr>
+ <t t-foreach="sale_order.amount_by_group" t-as="amount_by_group">
+ <tr>
+ <t t-if="amount_by_group[3] == 1 and sale_order.amount_untaxed == amount_by_group[2]">
+ <td>
+ <span t-esc="amount_by_group[0]"/>
+ <span>&amp;nbsp;<span>on</span>&amp;nbsp;<t t-esc="amount_by_group[2]" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/></span>
+ </td>
+ <td class="text-right">
+ <span t-esc="amount_by_group[1]"
+ t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
+ </td>
+ </t>
+ <t t-else ="">
+ <td>
+ <span t-esc="amount_by_group[0]"/>
+ </td>
+ <td class="text-right">
+ <span t-esc="amount_by_group[1]"
+ t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
+ </td>
+ </t>
+ </tr>
+ </t>
+ <tr class="border-black">
+ <td><strong>Total</strong></td>
+ <td class="text-right">
+ <span data-id="total_amount" t-field="sale_order.amount_total" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
+ </td>
+ </tr>
+ </table>
+ </template>
</odoo>
diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml
index 0a5df73d185..b2c3a1009ad 100644
--- a/addons/sale/views/sale_views.xml
+++ b/addons/sale/views/sale_views.xml
@@ -895,7 +895,7 @@
</div>
<group>
<group>
- <field name="order_id"/>
+ <field name="order_id" readonly="1"/>
<field name="product_id" readonly="1"/>
<field name="name" readonly="1"/>
<field name="product_uom_qty" readonly="1"/>
diff --git a/addons/sale_management/controllers/portal.py b/addons/sale_management/controllers/portal.py
index 9547d8e182b..eb4e945b8d3 100644
--- a/addons/sale_management/controllers/portal.py
+++ b/addons/sale_management/controllers/portal.py
@@ -1,7 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from functools import partial
+
from odoo import http
+from odoo.tools import formatLang
from odoo.exceptions import AccessError, MissingError
from odoo.http import request
from odoo.addons.sale.controllers.portal import CustomerPortal
@@ -11,6 +14,13 @@ class CustomerPortal(CustomerPortal):
@http.route(['/my/orders/<int:order_id>/update_line'], type='json', auth="public", website=True)
def update(self, line_id, remove=False, unlink=False, order_id=None, access_token=None, **post):
+ values = self.update_line_dict(line_id, remove, unlink, order_id, access_token, **post)
+ if values:
+ return [values['order_line_product_uom_qty'], values['order_amount_total']]
+ return values
+
+ @http.route(['/my/orders/<int:order_id>/update_line_dict'], type='json', auth="public", website=True)
+ def update_line_dict(self, line_id, remove=False, unlink=False, order_id=None, access_token=None, input_quantity=False, **kwargs):
try:
order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token)
except (AccessError, MissingError):
@@ -24,10 +34,34 @@ class CustomerPortal(CustomerPortal):
if unlink:
order_line.unlink()
return False # return False to reload the page, the line must move back to options and the JS doesn't handle it
- number = -1 if remove else 1
- quantity = order_line.product_uom_qty + number
+
+ if input_quantity is not False:
+ quantity = input_quantity
+ else:
+ number = -1 if remove else 1
+ quantity = order_line.product_uom_qty + number
+
+ if quantity < 0:
+ quantity = 0.0
order_line.write({'product_uom_qty': quantity})
- return [str(quantity), str(order_sudo.amount_total)]
+ currency = order_sudo.currency_id
+ format_price = partial(formatLang, request.env, digits=currency.decimal_places)
+
+ results = {
+ 'order_line_product_uom_qty': str(quantity),
+ 'order_line_price_total': format_price(order_line.price_total),
+ 'order_line_price_subtotal': format_price(order_line.price_subtotal),
+ 'order_amount_total': format_price(order_sudo.amount_total),
+ 'order_amount_untaxed': format_price(order_sudo.amount_untaxed),
+ 'order_amount_tax': format_price(order_sudo.amount_tax),
+ 'order_amount_undiscounted': format_price(order_sudo.amount_undiscounted),
+ }
+ try:
+ results['order_totals_table'] = request.env['ir.ui.view'].render_template('sale.sale_order_portal_content_totals_table', {'sale_order': order_sudo})
+ except ValueError:
+ pass
+
+ return results
@http.route(["/my/orders/<int:order_id>/add_option/<int:option_id>"], type='http', auth="public", website=True)
def add(self, order_id, option_id, access_token=None, **post):
diff --git a/addons/sale_management/static/src/js/sale_management.js b/addons/sale_management/static/src/js/sale_management.js
index 87d2c94c5d8..7092ac06355 100644
--- a/addons/sale_management/static/src/js/sale_management.js
+++ b/addons/sale_management/static/src/js/sale_management.js
@@ -8,40 +8,149 @@ publicWidget.registry.SaleUpdateLineButton = publicWidget.Widget.extend({
events: {
'click': '_onClick',
},
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
/**
- * @private
+ * @override
*/
- _onClick: function (ev) {
- ev.preventDefault();
+ start: function () {
var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ self.elems = self._getUpdatableElements();
+ self.elems.$lineQuantity.change(function (ev) {
+ var quantity = parseInt(this.value);
+ self._onChangeQuantity(quantity);
+ });
+ });
+ },
+ /**
+ * Process the change in line quantity
+ *
+ * @private
+ * @param {Int} quantity, the new quantity of the line
+ * If not present it will increment/decrement the existing quantity
+ */
+ _onChangeQuantity: function (quantity) {
var href = this.$el.attr("href");
var orderID = href.match(/my\/orders\/([0-9]+)/);
var lineID = href.match(/update_line\/([0-9]+)/);
var params = {
- 'line_id': lineID[1],
- 'remove': self.$el.is('[href*="remove"]'),
- 'unlink': self.$el.is('[href*="unlink"]'),
+ 'line_id': parseInt(lineID[1]),
+ 'remove': this.$el.is('[href*="remove"]'),
+ 'unlink': this.$el.is('[href*="unlink"]'),
+ 'input_quantity': quantity >= 0 ? quantity : false,
};
- var token = href.match(/token=(.*)/);
+ var token = href.match(/token=([\w\d-]*)/)[1];
if (token) {
params['access_token'] = token;
}
- var url = "/my/orders/" + parseInt(orderID[1]) + "/update_line";
- this._rpc({
+
+ orderID = parseInt(orderID[1]);
+ this._callUpdateLineRoute(orderID, params).then(this._updateOrderValues.bind(this));
+ },
+ /**
+ * Reacts to the click on the -/+ buttons
+ *
+ * @param {Event} ev
+ */
+ _onClick: function (ev) {
+ ev.preventDefault();
+ return this._onChangeQuantity();
+ },
+ /**
+ * Calls the route to get updated values of the line and order
+ * when the quantity of a product has changed
+ *
+ * @private
+ * @param {integer} order_id
+ * @param {Object} params
+ * @return {Deferred}
+ */
+ _callUpdateLineRoute: function (order_id, params) {
+ var url = "/my/orders/" + order_id + "/update_line_dict";
+ return this._rpc({
route: url,
params: params,
- }).then(function (data) {
- if (!data) {
- window.location.reload();
- }
- self.$el.closest('.input-group').find('.js_quantity').val(data[0]);
- $('[data-id="total_amount"] > span').html(data[1]);
});
},
+ /**
+ * Processes data from the server to update the UI
+ *
+ * @private
+ * @param {Object} data: contains order and line updated values
+ */
+ _updateOrderValues: function (data) {
+ if (!data) {
+ window.location.reload();
+ }
+
+ var orderAmountTotal = data.order_amount_total;
+ var orderAmountUntaxed = data.order_amount_untaxed;
+ var orderAmountTax = data.order_amount_tax;
+ var orderAmountUndiscounted = data.order_amount_undiscounted;
+ var orderTotalsTable = $(data.order_totals_table);
+
+ var lineProductUomQty = data.order_line_product_uom_qty;
+ var linePriceTotal = data.order_line_price_total;
+ var linePriceSubTotal = data.order_line_price_subtotal;
+
+ this.elems.$lineQuantity.val(lineProductUomQty);
+
+ if (this.elems.$linePriceTotal.length && linePriceTotal !== undefined) {
+ this.elems.$linePriceTotal.text(linePriceTotal);
+ }
+ if (this.elems.$linePriceSubTotal.length && linePriceSubTotal !== undefined) {
+ this.elems.$linePriceSubTotal.text(linePriceSubTotal);
+ }
+
+ if (orderAmountUntaxed !== undefined) {
+ this.elems.$orderAmountUntaxed.text(orderAmountUntaxed);
+ }
+
+ if (orderAmountTotal !== undefined) {
+ this.elems.$orderAmountTotal.text(orderAmountTotal);
+ }
+
+ if (orderAmountUndiscounted !== undefined) {
+ this.elems.$orderAmountUndiscounted.text(orderAmountUndiscounted);
+ }
+ if (orderTotalsTable) {
+ this.elems.$orderTotalsTable.find('table').replaceWith(orderTotalsTable);
+ }
+ },
+ /**
+ * Locate in the DOM the elements to update
+ * Mostly for compatibility, when the module has not been upgraded
+ * In that case, we need to fall back to some other elements
+ *
+ * @private
+ * @return {Object}: Jquery elements to update
+ */
+ _getUpdatableElements: function () {
+ var $parentTr = this.$el.parents('tr:first');
+ var $linePriceTotal = $parentTr.find('.oe_order_line_price_total .oe_currency_value');
+ var $linePriceSubTotal = $parentTr.find('.oe_order_line_price_subtotal .oe_currency_value');
+
+ if (!$linePriceTotal.length && !$linePriceSubTotal.length) {
+ $linePriceTotal = $linePriceSubTotal = $parentTr.find('.oe_currency_value').last();
+ }
+
+ var $orderAmountUntaxed = $('[data-id="total_untaxed"]').find('span, b');
+ var $orderAmountTotal = $('[data-id="total_amount"]').find('span, b');
+ var $orderAmountUndiscounted = $('[data-id="amount_undiscounted"]').find('span, b');
+
+ if (!$orderAmountUntaxed.length) {
+ $orderAmountUntaxed = $orderAmountTotal.eq(1);
+ $orderAmountTotal = $orderAmountTotal.eq(0).add($orderAmountTotal.eq(2));
+ }
+
+ return {
+ $lineQuantity: this.$el.closest('.input-group').find('.js_quantity'),
+ $linePriceSubTotal: $linePriceSubTotal,
+ $linePriceTotal: $linePriceTotal,
+ $orderAmountUntaxed: $orderAmountUntaxed,
+ $orderAmountTotal: $orderAmountTotal,
+ $orderTotalsTable: $('#total'),
+ $orderAmountUndiscounted: $orderAmountUndiscounted,
+ };
+ }
});
});
diff --git a/addons/sale_management/tests/test_sale_ui.py b/addons/sale_management/tests/test_sale_ui.py
index cf107b02ebc..d08d5d60f7a 100644
--- a/addons/sale_management/tests/test_sale_ui.py
+++ b/addons/sale_management/tests/test_sale_ui.py
@@ -7,3 +7,5 @@ class TestUi(odoo.tests.HttpCase):
def test_01_sale_tour(self):
self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('sale_tour')", "odoo.__DEBUG__.services['web_tour.tour'].tours.sale_tour.ready", login="admin")
+
+ self.env.ref('account.group_show_line_subtotals_tax_included').users |= admin \ No newline at end of file
diff --git a/addons/sale_quotation_builder/views/sale_order_views.xml b/addons/sale_quotation_builder/views/sale_order_views.xml
index e46a0cdd68b..ff2eb495f49 100644
--- a/addons/sale_quotation_builder/views/sale_order_views.xml
+++ b/addons/sale_quotation_builder/views/sale_order_views.xml
@@ -7,6 +7,13 @@
<field name="inherit_id" ref="sale_management.sale_order_form_quote"/>
<field name="arch" type="xml">
+ <xpath expr="//page/field[@name='order_line']/tree/field[@name='name']" position="after">
+ <field name="website_description" invisible="1" readonly="1"/>
+ </xpath>
+ <xpath expr="//page/field[@name='order_line']/form/field[@name='name']" position="after">
+ <field name="website_description" invisible="1" readonly="1"/>
+ </xpath>
+
<xpath expr="//button[@name='button_add_to_order']" position="after">
<field name="website_description" invisible="1"/>
</xpath>
diff --git a/addons/sale_timesheet/models/account.py b/addons/sale_timesheet/models/account.py
index bfdc17f7d27..81895718a96 100644
--- a/addons/sale_timesheet/models/account.py
+++ b/addons/sale_timesheet/models/account.py
@@ -57,11 +57,15 @@ class AccountAnalyticLine(models.Model):
@api.multi
def write(self, values):
# prevent to update invoiced timesheets if one line is of type delivery
+ self._check_can_write(values)
+ result = super(AccountAnalyticLine, self).write(values)
+ return result
+
+ @api.multi
+ def _check_can_write(self, values):
if self.sudo().filtered(lambda aal: aal.so_line.product_id.invoice_policy == "delivery") and self.filtered(lambda timesheet: timesheet.timesheet_invoice_id):
if any([field_name in values for field_name in ['unit_amount', 'employee_id', 'project_id', 'task_id', 'so_line', 'amount', 'date']]):
raise UserError(_('You can not modify already invoiced timesheets (linked to a Sales order items invoiced on Time and material).'))
- result = super(AccountAnalyticLine, self).write(values)
- return result
@api.model
def _timesheet_preprocess(self, values):
diff --git a/addons/sale_timesheet/report/project_profitability_report_analysis.py b/addons/sale_timesheet/report/project_profitability_report_analysis.py
index 2ec12acea09..2dc397495aa 100644
--- a/addons/sale_timesheet/report/project_profitability_report_analysis.py
+++ b/addons/sale_timesheet/report/project_profitability_report_analysis.py
@@ -68,7 +68,7 @@ class ProfitabilityAnalysis(models.Model):
ELSE 0.0
END AS amount_untaxed_to_invoice,
CASE
- WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (SOL.untaxed_amount_invoiced / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
+ WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (COALESCE(SOL.untaxed_amount_invoiced, COST_SUMMARY.downpayment_invoiced) / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
ELSE 0.0
END AS amount_untaxed_invoiced,
COST_SUMMARY.timesheet_unit_amount AS timesheet_unit_amount,
@@ -83,7 +83,8 @@ class ProfitabilityAnalysis(models.Model):
sale_line_id,
SUM(timesheet_unit_amount) AS timesheet_unit_amount,
SUM(timesheet_cost) AS timesheet_cost,
- SUM(expense_cost) AS expense_cost
+ SUM(expense_cost) AS expense_cost,
+ SUM(downpayment_invoiced) AS downpayment_invoiced
FROM (
SELECT
P.id AS project_id,
@@ -91,7 +92,8 @@ class ProfitabilityAnalysis(models.Model):
TS.so_line AS sale_line_id,
SUM(TS.unit_amount) AS timesheet_unit_amount,
SUM(TS.amount) AS timesheet_cost,
- 0.0 AS expense_cost
+ 0.0 AS expense_cost,
+ 0.0 AS downpayment_invoiced
FROM account_analytic_line TS, project_project P
WHERE TS.project_id IS NOT NULL AND P.id = TS.project_id AND P.active = 't' AND P.allow_timesheets = 't'
GROUP BY P.id, TS.so_line
@@ -104,7 +106,8 @@ class ProfitabilityAnalysis(models.Model):
AAL.so_line AS sale_line_id,
0.0 AS timesheet_unit_amount,
0.0 AS timesheet_cost,
- SUM(AAL.amount) AS expense_cost
+ SUM(AAL.amount) AS expense_cost,
+ 0.0 AS downpayment_invoiced
FROM project_project P
LEFT JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id
@@ -116,10 +119,47 @@ class ProfitabilityAnalysis(models.Model):
SELECT
P.id AS project_id,
P.analytic_account_id AS analytic_account_id,
+ MY_SOLS.id AS sale_line_id,
+ 0.0 AS timesheet_unit_amount,
+ 0.0 AS timesheet_cost,
+ 0.0 AS expense_cost,
+ CASE WHEN MY_SOLS.invoice_status = 'invoiced' THEN MY_SOLS.price_reduce ELSE 0.0 END AS downpayment_invoiced
+ FROM project_project P
+ LEFT JOIN sale_order_line MY_SOL ON P.sale_line_id = MY_SOL.id
+ LEFT JOIN sale_order MY_S ON MY_SOL.order_id = MY_S.id
+ LEFT JOIN sale_order_line MY_SOLS ON MY_SOLS.order_id = MY_S.id
+ WHERE MY_SOLS.is_downpayment = 't'
+ GROUP BY P.id, MY_SOLS.id
+
+ UNION
+
+ SELECT
+ P.id AS project_id,
+ P.analytic_account_id AS analytic_account_id,
+ OLIS.id AS sale_line_id,
+ 0.0 AS timesheet_unit_amount,
+ 0.0 AS timesheet_cost,
+ OLIS.price_reduce AS expense_cost,
+ 0.0 AS downpayment_invoiced
+ FROM project_project P
+ LEFT JOIN account_analytic_account ANAC ON P.analytic_account_id = ANAC.id
+ LEFT JOIN account_analytic_line ANLI ON ANAC.id = ANLI.account_id
+ LEFT JOIN sale_order_line OLI ON P.sale_line_id = OLI.id
+ LEFT JOIN sale_order ORD ON OLI.order_id = ORD.id
+ LEFT JOIN sale_order_line OLIS ON ORD.id = OLIS.order_id
+ WHERE OLIS.product_id = ANLI.product_id AND OLIS.is_downpayment = 't' AND ANLI.amount < 0.0 AND ANLI.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
+ GROUP BY P.id, OLIS.id
+
+ UNION
+
+ SELECT
+ P.id AS project_id,
+ P.analytic_account_id AS analytic_account_id,
SOL.id AS sale_line_id,
0.0 AS timesheet_unit_amount,
0.0 AS timesheet_cost,
- 0.0 AS expense_cost
+ 0.0 AS expense_cost,
+ 0.0 AS downpayment_invoiced
FROM sale_order_line SOL
INNER JOIN project_project P ON SOL.project_id = P.id
WHERE P.active = 't' AND P.allow_timesheets = 't'
@@ -132,7 +172,8 @@ class ProfitabilityAnalysis(models.Model):
SOL.id AS sale_line_id,
0.0 AS timesheet_unit_amount,
0.0 AS timesheet_cost,
- 0.0 AS expense_cost
+ 0.0 AS expense_cost,
+ 0.0 AS downpayment_invoiced
FROM sale_order_line SOL
INNER JOIN project_task T ON SOL.task_id = T.id
INNER JOIN project_project P ON P.id = T.project_id
diff --git a/addons/sale_timesheet/security/sale_timesheet_security.xml b/addons/sale_timesheet/security/sale_timesheet_security.xml
index f0f1f070eda..3f108758e60 100644
--- a/addons/sale_timesheet/security/sale_timesheet_security.xml
+++ b/addons/sale_timesheet/security/sale_timesheet_security.xml
@@ -3,17 +3,6 @@
<data noupdate="1">
- <record id="account_analytic_line_rule_billing_user" model="ir.rule">
- <field name="name">account.analytic.line.billing.user</field>
- <field name="model_id" ref="analytic.model_account_analytic_line"/>
- <field name="domain_force">[(1, '=', 1)]</field>
- <field name="groups" eval="[(4, ref('account.group_account_invoice'))]"/>
- <field name="perm_create" eval="1"/>
- <field name="perm_write" eval="1"/>
- <field name="perm_unlink" eval="1"/>
- <field name="perm_read" eval="0"/>
- </record>
-
<record id="sale_order_line_rule_project_manager" model="ir.rule">
<field name="name">Project Manager Sales Orders Line</field>
<field name="model_id" ref="sale.model_sale_order_line"/>
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index 19da209adfb..694ceecdb82 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -908,8 +908,13 @@ class StockMove(models.Model):
"""
assigned_moves = self.env['stock.move']
partially_available_moves = self.env['stock.move']
+ # Read the `reserved_availability` field of the moves out of the loop to prevent unwanted
+ # cache invalidation when actually reserving the move.
+ reserved_availability = {move: move.reserved_availability for move in self}
+ roundings = {move: move.product_id.uom_id.rounding for move in self}
for move in self.filtered(lambda m: m.state in ['confirmed', 'waiting', 'partially_available']):
- missing_reserved_uom_quantity = move.product_uom_qty - move.reserved_availability
+ rounding = roundings[move]
+ missing_reserved_uom_quantity = move.product_uom_qty - reserved_availability[move]
missing_reserved_quantity = move.product_uom._compute_quantity(missing_reserved_uom_quantity, move.product_id.uom_id, rounding_method='HALF-UP')
if move.location_id.should_bypass_reservation()\
or move.product_id.type == 'consu':
@@ -936,7 +941,7 @@ class StockMove(models.Model):
continue
# If we don't need any quantity, consider the move assigned.
need = missing_reserved_quantity
- if float_is_zero(need, precision_rounding=move.product_id.uom_id.rounding):
+ if float_is_zero(need, precision_rounding=rounding):
assigned_moves |= move
continue
# Reserve new quants and create move lines accordingly.
@@ -945,9 +950,9 @@ class StockMove(models.Model):
if available_quantity <= 0:
continue
taken_quantity = move._update_reserved_quantity(need, available_quantity, move.location_id, package_id=forced_package_id, strict=False)
- if float_is_zero(taken_quantity, precision_rounding=move.product_id.uom_id.rounding):
+ if float_is_zero(taken_quantity, precision_rounding=rounding):
continue
- if need == taken_quantity:
+ if float_compare(need, taken_quantity, precision_rounding=rounding) == 0:
assigned_moves |= move
else:
partially_available_moves |= move
@@ -1010,12 +1015,12 @@ class StockMove(models.Model):
# this case `quantity` is directly the quantity on the quants themselves.
available_quantity = self.env['stock.quant']._get_available_quantity(
move.product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True)
- if float_is_zero(available_quantity, precision_rounding=move.product_id.uom_id.rounding):
+ if float_is_zero(available_quantity, precision_rounding=rounding):
continue
taken_quantity = move._update_reserved_quantity(need, min(quantity, available_quantity), location_id, lot_id, package_id, owner_id)
- if float_is_zero(taken_quantity, precision_rounding=move.product_id.uom_id.rounding):
+ if float_is_zero(taken_quantity, precision_rounding=rounding):
continue
- if need - taken_quantity == 0.0:
+ if float_is_zero(need - taken_quantity, precision_rounding=rounding):
assigned_moves |= move
break
partially_available_moves |= move
diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py
index 7e04f92085e..631bf4379cf 100644
--- a/addons/stock/models/stock_move_line.py
+++ b/addons/stock/models/stock_move_line.py
@@ -16,12 +16,12 @@ class StockMoveLine(models.Model):
_order = "result_package_id desc, id"
picking_id = fields.Many2one(
- 'stock.picking', 'Stock Picking',
+ 'stock.picking', 'Stock Picking', auto_join=True,
help='The stock operation where the packing has been made')
move_id = fields.Many2one(
'stock.move', 'Stock Move',
help="Change to a better name", index=True)
- company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env['res.users']._get_company(), readonly=True)
+ company_id = fields.Many2one('res.company', string='Company', related='move_id.company_id', store=True, readonly=True)
product_id = fields.Many2one('product.product', 'Product', ondelete="cascade")
product_uom_id = fields.Many2one('uom.uom', 'Unit of Measure', required=True)
product_qty = fields.Float(
@@ -75,12 +75,6 @@ class StockMoveLine(models.Model):
if line.lot_id and line.product_id != line.lot_id.product_id:
raise ValidationError(_('This lot %s is incompatible with this product %s' % (line.lot_id.name, line.product_id.display_name)))
- @api.constrains('company_id', 'move_id')
- def _check_same_company_id(self):
- for line in self:
- if line.move_id and line.move_id.company_id != line.company_id:
- raise ValidationError(_('The move line a a different company than its move'))
-
@api.one
def _set_product_qty(self):
""" The meaning of product_qty field changed lately and is now a functional field computing the quantity
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index 275a0cd4e35..c2e5bf0e203 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -1132,3 +1132,12 @@ class Picking(models.Model):
action['domain'] = [('id', 'in', packages.ids)]
action['context'] = {'picking_id': self.id}
return action
+
+ def action_picking_move_tree(self):
+ action = self.env.ref('stock.stock_move_action').read()[0]
+ action['views'] = [
+ (self.env.ref('stock.view_picking_move_tree').id, 'tree'),
+ ]
+ action['context'] = self.env.context
+ action['domain'] = [('picking_id', 'in', self.ids)]
+ return action
diff --git a/addons/stock/models/stock_rule.py b/addons/stock/models/stock_rule.py
index 96374d4830a..c8e29d77f53 100644
--- a/addons/stock/models/stock_rule.py
+++ b/addons/stock/models/stock_rule.py
@@ -376,14 +376,21 @@ class ProcurementGroup(models.Model):
('product_id', '=', values['product_id'].id)]
@api.model
+ def _get_moves_to_assign_domain(self):
+ return expression.AND([
+ [('state', 'in', ['confirmed', 'partially_available'])],
+ [('product_uom_qty', '!=', 0.0)]
+ ])
+
+ @api.model
def _run_scheduler_tasks(self, use_new_cursor=False, company_id=False):
# Minimum stock rules
self.sudo()._procure_orderpoint_confirm(use_new_cursor=use_new_cursor, company_id=company_id)
# Search all confirmed stock_moves and try to assign them
- moves_to_assign = self.env['stock.move'].search([
- ('state', 'in', ['confirmed', 'partially_available']), ('product_uom_qty', '!=', 0.0)
- ], limit=None, order='priority desc, date_expected asc')
+ domain = self._get_moves_to_assign_domain()
+ moves_to_assign = self.env['stock.move'].search(domain, limit=None,
+ order='priority desc, date_expected asc')
for moves_chunk in split_every(100, moves_to_assign.ids):
self.env['stock.move'].browse(moves_chunk)._action_assign()
if use_new_cursor:
diff --git a/addons/stock/models/stock_traceability.py b/addons/stock/models/stock_traceability.py
index b1345bed60f..29e8ac22450 100644
--- a/addons/stock/models/stock_traceability.py
+++ b/addons/stock/models/stock_traceability.py
@@ -21,7 +21,7 @@ class MrpStockReport(models.TransientModel):
_description = 'Traceability Report'
@api.model
- def _get_move_lines(self, move_lines):
+ def _get_move_lines(self, move_lines, line_id=None):
lines_seen = move_lines
lines_todo = list(move_lines)
while lines_todo:
@@ -43,7 +43,8 @@ class MrpStockReport(models.TransientModel):
])
else:
continue
- lines_todo += list(lines)
+ if line_id is None or line_id in lines.ids:
+ lines_todo += list(lines)
lines_seen |= lines
return lines_seen - move_lines
@@ -178,7 +179,7 @@ class MrpStockReport(models.TransientModel):
lines = move_lines
else:
# Traceability in case of consumed in.
- lines = self._get_move_lines(move_line)
+ lines = self._get_move_lines(move_line, line_id=line_id)
for line in lines:
unfoldable = False
if line.consume_line_ids or ( line.lot_id and self._get_move_lines(line) and model != "stock.production.lot"):
diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py
index 40600a12a4e..80b7a87d982 100644
--- a/addons/stock/models/stock_warehouse.py
+++ b/addons/stock/models/stock_warehouse.py
@@ -499,40 +499,48 @@ class Warehouse(models.Model):
delivery_steps = vals.get('delivery_steps', def_values['delivery_steps'])
code = vals.get('code') or code
code = code.replace(' ', '').upper()
+ company_id = vals.get('company_id', self.company_id.id)
sub_locations = {
'lot_stock_id': {
'name': _('Stock'),
'active': True,
'usage': 'internal',
- 'barcode': code + '-STOCK'
+ 'barcode': self._valid_barcode(code + '-STOCK', company_id)
},
'wh_input_stock_loc_id': {
'name': _('Input'),
'active': reception_steps != 'one_step',
'usage': 'internal',
- 'barcode': code + '-INPUT'
+ 'barcode': self._valid_barcode(code + '-INPUT', company_id)
},
'wh_qc_stock_loc_id': {
'name': _('Quality Control'),
'active': reception_steps == 'three_steps',
'usage': 'internal',
- 'barcode': code + '-QUALITY'
+ 'barcode': self._valid_barcode(code + '-QUALITY', company_id)
},
'wh_output_stock_loc_id': {
'name': _('Output'),
'active': delivery_steps != 'ship_only',
'usage': 'internal',
- 'barcode': code + '-OUTPUT'
+ 'barcode': self._valid_barcode(code + '-OUTPUT', company_id)
},
'wh_pack_stock_loc_id': {
'name': _('Packing Zone'),
'active': delivery_steps == 'pick_pack_ship',
'usage': 'internal',
- 'barcode': code + '-PACKING'
+ 'barcode': self._valid_barcode(code + '-PACKING', company_id)
},
}
return sub_locations
+ def _valid_barcode(self, barcode, company_id):
+ location = self.env['stock.location'].with_context(active_test=False).search([
+ ('barcode', '=', barcode),
+ ('company_id', '=', company_id)
+ ])
+ return not location and barcode
+
def _create_missing_locations(self, vals):
""" It could happen that the user delete a mandatory location or a
module with new locations was installed after some warehouses creation.
diff --git a/addons/stock/security/stock_security.xml b/addons/stock/security/stock_security.xml
index b738306073a..a851beb0cfc 100644
--- a/addons/stock/security/stock_security.xml
+++ b/addons/stock/security/stock_security.xml
@@ -106,7 +106,7 @@
<field name="name">stock_move_line multi-company</field>
<field name="model_id" search="[('model','=','stock.move.line')]" model="ir.model"/>
<field name="global" eval="True"/>
- <field name="domain_force">[('company_id','child_of',[user.company_id.id])]</field>
+ <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record model="ir.rule" id="stock_quant_rule">
diff --git a/addons/stock/static/src/xml/stock_traceability_report_backend.xml b/addons/stock/static/src/xml/stock_traceability_report_backend.xml
index e2aa016b5e6..84e9552d507 100644
--- a/addons/stock/static/src/xml/stock_traceability_report_backend.xml
+++ b/addons/stock/static/src/xml/stock_traceability_report_backend.xml
@@ -3,7 +3,7 @@
<templates>
<t t-name="stockReports.buttons">
- <button type="button" class='btn btn-primary o_stock-widget-pdf'>PRINT</button>
+ <button type="button" class='btn btn-primary o_stock-widget-pdf d-none'>PRINT</button>
</t>
<div role="dialog" t-name='stockReports.errorModal' class="modal" id="editable_error" tabindex="-1" data-backdrop="static" style="z-index:9999;">
diff --git a/addons/stock/views/stock_move_views.xml b/addons/stock/views/stock_move_views.xml
index a89b280ddbc..44b5802f497 100644
--- a/addons/stock/views/stock_move_views.xml
+++ b/addons/stock/views/stock_move_views.xml
@@ -66,12 +66,44 @@
</field>
</record>
+ <record id="view_picking_move_tree" model="ir.ui.view">
+ <field name="name">stock.picking.move.tree</field>
+ <field name="model">stock.move</field>
+ <field eval="50" name="priority"/>
+ <field name="arch" type="xml">
+ <tree decoration-danger="state != 'done' and quantity_done > reserved_availability and show_reserved_availability" decoration-muted="scrapped == True or state == 'cancel' or (state == 'done' and is_locked == True)" string="Stock Moves" editable="bottom">
+ <field name="name" invisible="1"/>
+ <field name="date_expected" invisible="1"/>
+ <field name="state" invisible="1" readonly="0"/>
+ <field name="picking_type_id" invisible="1"/>
+ <field name="location_id" invisible="1"/>
+ <field name="location_dest_id" invisible="1"/>
+ <field name="scrapped" invisible="1"/>
+ <field name="picking_code" invisible="1"/>
+ <field name="product_type" invisible="1"/>
+ <field name="show_details_visible" invisible="1"/>
+ <field name="show_reserved_availability" invisible="1"/>
+ <field name="show_operations" invisible="1" readonly="1"/>
+ <field name="additional" invisible="1"/>
+ <field name="has_move_lines" invisible="1"/>
+ <field name="is_locked" invisible="1"/>
+ <field name="product_id" required="1" attrs="{'readonly': ['|', '&amp;', ('state', '!=', 'draft'), ('additional', '=', False), ('has_move_lines', '=', True)]}"/>
+ <field name="is_initial_demand_editable" invisible="1"/>
+ <field name="is_quantity_done_editable" invisible="1"/>
+ <field name="product_uom_qty" string="Initial Demand" attrs="{'readonly': [('is_initial_demand_editable', '=', False)]}"/>
+ <field name="reserved_availability" string="Reserved"/>
+ <field name="quantity_done" string="Done" attrs="{'readonly': [('is_quantity_done_editable', '=', False)]}"/>
+ <field name="product_uom" attrs="{'readonly': [('state', '!=', 'draft'), ('additional', '=', False)]}" options="{'no_open': True, 'no_create': True}" string="Unit of Measure" groups="uom.group_uom"/>
+ </tree>
+ </field>
+ </record>
+
<record id="view_move_kandan" model="ir.ui.view">
<field name="name">stock.move.kanban</field>
<field name="model">stock.move</field>
<field name="priority">10</field>
<field name="arch" type="xml">
- <kanban class="o_kanban_mobile" create="0">
+ <kanban class="o_kanban_mobile">
<field name="product_id"/>
<field name="show_details_visible"/>
<field name="product_uom_qty"/>
diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml
index 7c617e27dc7..19cbed3599b 100644
--- a/addons/stock/views/stock_picking_views.xml
+++ b/addons/stock/views/stock_picking_views.xml
@@ -243,6 +243,19 @@
class="oe_stat_button" icon="fa-cubes"
attrs="{'invisible': [('has_packages', '=', False)]}"/>
<button name="%(action_stock_report)d" icon="fa-arrow-up" class="oe_stat_button" string="Traceability Report" type="action" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('has_tracking', '=', False)]}" groups="stock.group_production_lot"/>
+ <!-- Use the following button to avoid onchange on one2many -->
+ <button name="action_picking_move_tree"
+ class="oe_stat_button"
+ icon="fa-arrows-v"
+ type="object"
+ help="List view of operations"
+ groups="base.group_no_one"
+ attrs="{'invisible': ['|', '&amp;', ('show_operations', '=', True), '|', ('is_locked', '=', True), ('state', '=', 'done'), '&amp;', ('state', '=', 'done'), ('is_locked', '=', True)]}"
+ context="{'picking_type_code': picking_type_code, 'default_picking_id': id, 'form_view_ref':'stock.view_move_picking_form', 'address_in_id': partner_id, 'default_picking_type_id': picking_type_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id}">
+ <div class="o_form_field o_stat_info">
+ <span class="o_stat_text">Operations</span>
+ </div>
+ </button>
</div>
<h1 class="d-none d-md-block">
<field name="name" attrs="{'invisible': [('name','=','/')]}" readonly="1"/>
diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py
index 3ba8f85bf26..9d1eb319286 100644
--- a/addons/stock_account/models/product.py
+++ b/addons/stock_account/models/product.py
@@ -115,8 +115,11 @@ class ProductProduct(models.Model):
def _get_fifo_candidates_in_move(self):
""" Find IN moves that can be used to value OUT moves.
"""
+ return self._get_fifo_candidates_in_move_with_company()
+
+ def _get_fifo_candidates_in_move_with_company(self, move_company_id=False):
self.ensure_one()
- domain = [('product_id', '=', self.id), ('remaining_qty', '>', 0.0)] + self.env['stock.move']._get_in_base_domain()
+ domain = [('product_id', '=', self.id), ('remaining_qty', '>', 0.0)] + self.env['stock.move']._get_in_base_domain(move_company_id)
candidates = self.env['stock.move'].search(domain, order='date, id')
return candidates
@@ -158,18 +161,30 @@ class ProductProduct(models.Model):
for row in res:
fifo_automated_values[(row[0], row[1])] = (row[2], row[3], list(row[4]))
- product_values = {product: 0 for product in self}
- product_move_ids = {product: [] for product in self}
+ product_values = {product.id: 0 for product in self}
+ product_move_ids = {product.id: [] for product in self}
+
if to_date:
domain = [('product_id', 'in', self.ids), ('date', '<=', to_date)] + StockMove._get_all_base_domain()
- for move in StockMove.search(domain).with_context(prefetch_fields=False):
- product_values[move.product_id] += move.value
- product_move_ids[move.product_id].append(move.id)
+ value_field_name = 'value'
else:
domain = [('product_id', 'in', self.ids)] + StockMove._get_all_base_domain()
- for move in StockMove.search(domain).with_context(prefetch_fields=False):
- product_values[move.product_id] += move.remaining_value
- product_move_ids[move.product_id].append(move.id)
+ value_field_name = 'remaining_value'
+
+ StockMove.check_access_rights('read')
+ query = StockMove._where_calc(domain)
+ StockMove._apply_ir_rules(query, 'read')
+ from_clause, where_clause, params = query.get_sql()
+ query_str = """
+ SELECT stock_move.product_id, SUM(COALESCE(stock_move.{}, 0.0)), ARRAY_AGG(stock_move.id)
+ FROM {}
+ WHERE {}
+ GROUP BY stock_move.product_id
+ """.format(value_field_name, from_clause, where_clause)
+ self.env.cr.execute(query_str, params)
+ for product_id, value, move_ids in self.env.cr.fetchall():
+ product_values[product_id] = value
+ product_move_ids[product_id] = move_ids
for product in self:
if product.cost_method in ['standard', 'average']:
@@ -185,9 +200,9 @@ class ProductProduct(models.Model):
elif product.cost_method == 'fifo':
if to_date:
if product.product_tmpl_id.valuation == 'manual_periodic':
- product.stock_value = product_values[product]
+ product.stock_value = product_values[product.id]
product.qty_at_date = product.with_context(company_owned=True, owner_id=False).qty_available
- product.stock_fifo_manual_move_ids = StockMove.browse(product_move_ids[product])
+ product.stock_fifo_manual_move_ids = StockMove.browse(product_move_ids[product.id])
elif product.product_tmpl_id.valuation == 'real_time':
valuation_account_id = product.categ_id.property_stock_valuation_account_id.id
value, quantity, aml_ids = fifo_automated_values.get((product.id, valuation_account_id)) or (0, 0, [])
@@ -195,10 +210,10 @@ class ProductProduct(models.Model):
product.qty_at_date = quantity
product.stock_fifo_real_time_aml_ids = self.env['account.move.line'].browse(aml_ids)
else:
- product.stock_value, moves = product_values[product], StockMove.browse(product_move_ids[product])
+ product.stock_value = product_values[product.id]
product.qty_at_date = product.with_context(company_owned=True, owner_id=False).qty_available
if product.product_tmpl_id.valuation == 'manual_periodic':
- product.stock_fifo_manual_move_ids = moves
+ product.stock_fifo_manual_move_ids = StockMove.browse(product_move_ids[product.id])
elif product.product_tmpl_id.valuation == 'real_time':
valuation_account_id = product.categ_id.property_stock_valuation_account_id.id
value, quantity, aml_ids = fifo_automated_values.get((product.id, valuation_account_id)) or (0, 0, [])
diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py
index 82981e657cb..b816669e706 100644
--- a/addons/stock_account/models/stock.py
+++ b/addons/stock_account/models/stock.py
@@ -266,7 +266,7 @@ class StockMove(models.Model):
# Find back incoming stock moves (called candidates here) to value this move.
qty_to_take_on_candidates = quantity or valued_quantity
- candidates = move.product_id._get_fifo_candidates_in_move()
+ candidates = move.product_id._get_fifo_candidates_in_move_with_company(move.company_id.id)
new_standard_price = 0
tmp_value = 0 # to accumulate the value taken on the candidates
for candidate in candidates:
@@ -320,6 +320,7 @@ class StockMove(models.Model):
def _run_valuation(self, quantity=None):
self.ensure_one()
+ value_to_return = 0
if self._is_in():
valued_move_lines = self.move_line_ids.filtered(lambda ml: not ml.location_id._should_be_valued() and ml.location_dest_id._should_be_valued() and not ml.owner_id)
valued_quantity = 0
@@ -377,7 +378,7 @@ class StockMove(models.Model):
'value': value_to_return,
'price_unit': price_unit if self._is_dropshipped() else -price_unit,
})
- return value_to_return
+ return value_to_return
def _action_done(self, cancel_backorder=False):
self.product_price_update_before_done()
diff --git a/addons/stock_dropshipping/models/purchase.py b/addons/stock_dropshipping/models/purchase.py
index 39196cd20bf..7fee03bc250 100644
--- a/addons/stock_dropshipping/models/purchase.py
+++ b/addons/stock_dropshipping/models/purchase.py
@@ -14,12 +14,12 @@ class PurchaseOrderLine(models.Model):
re['sale_line_id'] = self.sale_line_id.id
return res
- def _merge_in_existing_line(self, product_id, product_qty, product_uom, location_id, name, origin, values):
- if values.get('route_ids') and values['route_ids'] == self.env.ref('stock_dropshipping.route_drop_shipping'):
- return False
- return super(PurchaseOrderLine, self)._merge_in_existing_line(
- product_id=product_id, product_qty=product_qty, product_uom=product_uom,
- location_id=location_id, name=name, origin=origin, values=values)
+ def _find_candidate(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values):
+ # if this is defined, this is a dropshipping line, so no
+ # this is to correctly map delivered quantities to the so lines
+ lines = self.filtered(lambda po_line: po_line.sale_line_id.id == values['sale_line_id']) if values.get('sale_line_id') else self
+ return super(PurchaseOrderLine, lines)._find_candidate(product_id, product_qty, product_uom, location_id, name, origin, company_id, values)
+
class StockRule(models.Model):
_inherit = 'stock.rule'
diff --git a/addons/stock_dropshipping/tests/test_dropship.py b/addons/stock_dropshipping/tests/test_dropship.py
index 9837d93f0af..f70897f9eb8 100644
--- a/addons/stock_dropshipping/tests/test_dropship.py
+++ b/addons/stock_dropshipping/tests/test_dropship.py
@@ -48,8 +48,21 @@ class TestDropship(common.TransactionCase):
so.order_line.product_uom_qty = 2.00
self.assertAlmostEqual(po_line.product_qty, 2.00)
-
-class TestDropship(common.TransactionCase):
+ # Create a new so line
+ sol2 = self.env['sale.order.line'].create({
+ 'order_id': so.id,
+ 'name': prod.name,
+ 'product_id': prod.id,
+ 'product_uom_qty': 3.00,
+ 'product_uom': prod.uom_id.id,
+ 'price_unit': 12,
+ })
+ # there is a new line
+ pol2 = po.order_line - po_line
+ # the first line is unchanged
+ self.assertAlmostEqual(po_line.product_qty, 2.00)
+ # the new line matches the new line on the so
+ self.assertAlmostEqual(pol2.product_qty, sol2.product_uom_qty)
def test_00_dropship(self):
diff --git a/addons/survey/models/survey_survey.py b/addons/survey/models/survey_survey.py
index 9b52a86f2ed..b7002cf052c 100644
--- a/addons/survey/models/survey_survey.py
+++ b/addons/survey/models/survey_survey.py
@@ -239,7 +239,8 @@ class Survey(models.Model):
""" Ensure conditions to create new tokens are met. """
self.ensure_one()
if test_entry:
- if not user.has_group('survey.group_survey_manager') or not user.has_group('survey.group_survey_user'):
+ # the current user must have the access rights to survey
+ if not user.has_group('survey.group_survey_user'):
raise UserError(_('Creating test token is not allowed for you.'))
else:
if not self.active:
diff --git a/addons/survey/views/survey_templates.xml b/addons/survey/views/survey_templates.xml
index daa8e446718..26ac5981768 100644
--- a/addons/survey/views/survey_templates.xml
+++ b/addons/survey/views/survey_templates.xml
@@ -120,7 +120,7 @@
<!-- Back Button to redirect in form view of survey -->
<template id="back" name="Back">
- <div groups="survey.group_survey_manager" t-ignore="true" class="alert alert-info alert-dismissible rounded-0 fade show d-print-none css_editable_mode_hidden">
+ <div groups="survey.group_survey_manager" t-ignore="true" class="alert alert-info alert-dismissible rounded-0 mt16 fade show d-print-none css_editable_mode_hidden">
<div t-ignore="true" class="text-center">
<a t-attf-href="/web#view_type=form&amp;model=survey.survey&amp;id=#{survey.id}&amp;action=survey.action_survey_form"><i class="fa fa-fw fa-arrow-right"/><span t-if="answer and answer.test_entry">This is a test survey. </span>Edit Survey.</a>
</div>
@@ -145,7 +145,7 @@
<t t-call-assets="survey.survey_assets"/>
</t>
<body>
- <header>
+ <header t-if="not survey.certificate">
<div><title>Odoo</title></div>
<div class="text-center">
<img t-attf-src="/web/binary/company_logo" alt="Logo"/>
@@ -156,17 +156,15 @@
<t t-raw="0"/>
</main>
</body>
- <xpath expr="//footer" position="replace">
- <div class="container mt16 mb8">
- <div class="float-right" t-ignore="true" t-if="not editable">
- Create a <a target="_blank" href="https://www.odoo.com/page/website-builder">free website</a> with
- <a target="_blank" class="badge badge-danger" href="https://www.odoo.com/page/website-builder">Odoo</a>
- </div>
- <div class="float-left text-muted" itemscope="itemscope" itemtype="https://schema.org/Organization">
- Copyright &amp;copy; <span t-field="res_company.name" itemprop="name">Company name</span>
- </div>
+ <div t-if="not survey.certificate" class="container mt16 mb8">
+ <div class="float-right" t-ignore="true" t-if="not editable">
+ Create a <a target="_blank" href="https://www.odoo.com/page/website-builder">free website</a> with
+ <a target="_blank" class="badge badge-danger" href="https://www.odoo.com/page/website-builder">Odoo</a>
</div>
- </xpath>
+ <div class="float-left text-muted" itemscope="itemscope" itemtype="https://schema.org/Organization">
+ Copyright &amp;copy; <span t-field="res_company.name" itemprop="name">Company name</span>
+ </div>
+ </div>
</t>
</template>
diff --git a/addons/theme_bootswatch/static/lib/bootswatch/cyborg/_variables.scss b/addons/theme_bootswatch/static/lib/bootswatch/cyborg/_variables.scss
index fda2a4bcc74..dc354a087ac 100644
--- a/addons/theme_bootswatch/static/lib/bootswatch/cyborg/_variables.scss
+++ b/addons/theme_bootswatch/static/lib/bootswatch/cyborg/_variables.scss
@@ -45,7 +45,7 @@ $body-color: $gray-500 !default;
// Fonts
-$font-family-sans-serif: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !default;
+$font-family-sans-serif: "Roboto", "Noto", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !default;
$font-size-base: .875rem !default;
diff --git a/addons/web/doc/module.rst b/addons/web/doc/module.rst
index ffbe90d0933..ca808254567 100644
--- a/addons/web/doc/module.rst
+++ b/addons/web/doc/module.rst
@@ -412,7 +412,7 @@ responses":
.. note::
- By defaut, tests cases don't load templates either. We had not
+ By default, tests cases don't load templates either. We had not
needed to perform any template rendering before here, so we must
now enable templates loading via :js:attr:`the corresponding
option <TestOptions.templates>`.
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 5919f100bcc..e546eb02be0 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -802,7 +802,7 @@ var FieldBoolean = AbstractField.extend({
* @returns {jQuery} the focusable checkbox input
*/
getFocusableElement: function () {
- return this.$input || $();
+ return this.mode === 'readonly' ? $() : this.$input;
},
/**
* A boolean field is always set since false is a valid value.
@@ -1032,7 +1032,13 @@ var FieldFloatToggle = AbstractField.extend({
* @returns {float} The current formatted value
*/
_getDisplayedValue: function () {
- return parseFloat(this._formatValue(this.value));
+ // this.value is a plain float
+ // Matches what is in Database
+ var usrFormatValue = this._formatValue(this.value);
+ // usrFormatValue is string
+ // contains a float represented in a user specific format
+ // the float is the fraction by [this.factor] of this.value
+ return field_utils.parse['float'](usrFormatValue);
},
/**
* Formats the HTML input tag for edit mode and stores selection status.
@@ -1059,7 +1065,7 @@ var FieldFloatToggle = AbstractField.extend({
* one is not in the range, the next value of the closest one will be chosen.
*
* @private
- * @returns {number} The next formatted value in the range
+ * @returns {number} The next value in the range
*/
_nextValue: function () {
var range = this.nodeOptions.range;
@@ -1088,7 +1094,8 @@ var FieldFloatToggle = AbstractField.extend({
if (this.mode === 'edit') {
ev.stopPropagation(); // only stop propagation in edit mode
var next_val = this._nextValue();
- this._setValue(next_val.toString()); // will be parsed in _setValue
+ next_val = field_utils.format['float'](next_val);
+ this._setValue(next_val); // will be parsed in _setValue
}
},
diff --git a/addons/web/static/src/js/public/public_root.js b/addons/web/static/src/js/public/public_root.js
index 5d0483ad886..31b39e97abb 100644
--- a/addons/web/static/src/js/public/public_root.js
+++ b/addons/web/static/src/js/public/public_root.js
@@ -294,8 +294,8 @@ var PublicRoot = publicWidget.RootWidget.extend(ServiceProviderMixin, {
var $buttons = $(ev.currentTarget).find('button[type="submit"], a.a-submit');
_.each($buttons, function (btn) {
var $btn = $(btn);
- $btn.attr('data-loading-text', '<i class="fa fa-spinner fa-spin"></i> ' + $(btn).text());
- $btn.button('loading');
+ $btn.html('<i class="fa fa-spinner fa-spin"></i> ' + $btn.text());
+ $btn.prop('disabled', true);
});
},
/**
diff --git a/addons/web/static/src/js/tools/debug_manager.js b/addons/web/static/src/js/tools/debug_manager.js
index 54e98bbb87e..e5eff8d7fc1 100644
--- a/addons/web/static/src/js/tools/debug_manager.js
+++ b/addons/web/static/src/js/tools/debug_manager.js
@@ -436,6 +436,7 @@ DebugManager.include({
var fields = state.fields;
var fieldsInfo = state.fieldsInfo.form;
var fieldNamesInView = state.getFieldNames();
+ var fieldNamesOnlyOnView = ['message_attachment_count'];
var fieldsValues = state.data;
var modifierDatas = {};
_.each(fieldNamesInView, function (fieldName) {
@@ -444,6 +445,7 @@ DebugManager.include({
});
});
this.fields = _.chain(fieldNamesInView)
+ .difference(fieldNamesOnlyOnView)
.map(function (fieldName) {
var modifierData = modifierDatas[fieldName];
var invisibleOrReadOnly;
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index 804e6d6d1c9..2a22828b8d8 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -564,9 +564,16 @@ var BasicModel = AbstractModel.extend({
}
} else if (field.type === 'one2many' || field.type === 'many2many') {
if (options.raw) {
- relDataPoint = this.localData[data[fieldName]];
- relDataPoint = this._applyX2ManyOperations(relDataPoint);
- data[fieldName] = relDataPoint.res_ids;
+ if (typeof data[fieldName] === 'string') {
+ relDataPoint = this.localData[data[fieldName]];
+ relDataPoint = this._applyX2ManyOperations(relDataPoint);
+ data[fieldName] = relDataPoint.res_ids;
+ } else {
+ // no datapoint has been created yet (because the loading of relational
+ // data has been batched, and hasn't started yet), so the value is still
+ // the list of ids in the relation
+ data[fieldName] = data[fieldName] || [];
+ }
} else {
data[fieldName] = this.get(data[fieldName]) || [];
}
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 1bcf4fbb298..ba338a96e4d 100644
--- a/addons/web/static/src/js/views/calendar/calendar_model.js
+++ b/addons/web/static/src/js/views/calendar/calendar_model.js
@@ -60,10 +60,11 @@ return AbstractModel.extend({
end.add(-1, 'days');
}
+ var isDateEvent = this.fields[this.mapping.date_start].type === 'date';
// An "allDay" event without the "all_day" option is not considered
// as a 24h day. It's just a part of the day (by default: 7h-19h).
if (event.allDay) {
- if (!this.mapping.all_day) {
+ if (!this.mapping.all_day && !isDateEvent) {
if (event.r_start) {
start.hours(event.r_start.hours())
.minutes(event.r_start.minutes())
diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js
index 75c52ce81a7..3aeb1d2f6fb 100644
--- a/addons/web/static/src/js/widgets/date_picker.js
+++ b/addons/web/static/src/js/widgets/date_picker.js
@@ -258,6 +258,17 @@ 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_overridden.scss b/addons/web/static/src/scss/bootstrap_overridden.scss
index c1f8cbb44d3..50fde1fa6c2 100644
--- a/addons/web/static/src/scss/bootstrap_overridden.scss
+++ b/addons/web/static/src/scss/bootstrap_overridden.scss
@@ -61,7 +61,7 @@ $component-active-bg: $gray-200 !default;
//
// Font, line-height, and color for body text, headings, and more.
-$font-family-sans-serif: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif !default;
+$font-family-sans-serif: "Noto", "Lucida Grande", Helvetica, Verdana, Arial, sans-serif !default;
$font-size-base: $o-font-size-base !default;
$line-height-base: $o-line-height-base !default;
diff --git a/addons/web/static/src/scss/fonts.scss b/addons/web/static/src/scss/fonts.scss
index bfabf4fe6fc..0a8793c9a18 100644
--- a/addons/web/static/src/scss/fonts.scss
+++ b/addons/web/static/src/scss/fonts.scss
@@ -4,6 +4,36 @@
$lato-font-path: '../fonts/lato';
@mixin lato-font($type, $weight, $style) {
+ // Cyrillic: U+0400-04FF, U+0500-052F
+ @font-face {
+ font-family: 'Noto';
+ src: url('https://fonts.odoocdn.com/fonts/noto/NotoSans-#{$type}.woff2') format('woff2'),
+ url('https://fonts.odoocdn.com/fonts/noto/NotoSans-#{$type}.woff') format('woff'),
+ url('https://fonts.odoocdn.com/fonts/noto/NotoSans-#{$type}.ttf') format('truetype');
+ font-weight: $weight;
+ font-style: $style;
+ unicode-range: U+0400-04FF, U+0500-052F;
+ }
+ // Hebrew: U+0590-05FF, U+FB1D-FB4F
+ @font-face {
+ font-family: 'Noto';
+ src: url('https://fonts.odoocdn.com/fonts/noto/NotoSansHebrew-#{$type}.woff2') format('woff2'),
+ url('https://fonts.odoocdn.com/fonts/noto/NotoSansHebrew-#{$type}.woff') format('woff'),
+ url('https://fonts.odoocdn.com/fonts/noto/NotoSansHebrew-#{$type}.ttf') format('truetype');
+ font-weight: $weight;
+ font-style: $style;
+ unicode-range: U+0590-05FF, U+FB1D-FB4F;
+ }
+ // Arabic: U+0600-06FF, U+0750-077F
+ @font-face {
+ font-family: 'Noto';
+ src: url('https://fonts.odoocdn.com/fonts/noto/NotoSansArabic-#{$type}.woff2') format('woff2'),
+ url('https://fonts.odoocdn.com/fonts/noto/NotoSansArabic-#{$type}.woff') format('woff'),
+ url('https://fonts.odoocdn.com/fonts/noto/NotoSansArabic-#{$type}.ttf') format('truetype');
+ font-weight: $weight;
+ font-style: $style;
+ unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF;
+ }
@font-face {
font-family: 'Lato';
src: url('#{$lato-font-path}/Lato-#{$type}-webfont.eot');
diff --git a/addons/web/static/src/scss/layout_clean.scss b/addons/web/static/src/scss/layout_clean.scss
index 910abc763a4..0a8f4b065af 100644
--- a/addons/web/static/src/scss/layout_clean.scss
+++ b/addons/web/static/src/scss/layout_clean.scss
@@ -1,5 +1,5 @@
.o_clean_footer, .o_clean_header, .o_report_layout_clean {
- font-family: 'Lato';
+ font-family: 'Lato', 'Noto';
color: #000;
}
.o_clean_header {
diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js
index 01f431589fc..94200b95f5e 100644
--- a/addons/web/static/tests/views/calendar_tests.js
+++ b/addons/web/static/tests/views/calendar_tests.js
@@ -2322,6 +2322,69 @@ QUnit.module('Views', {
calendar.destroy();
});
+ QUnit.test('timzeone does not affect calendar with date field', async function (assert) {
+ assert.expect(8);
+
+ var calendar = await createView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch:
+ '<calendar date_start="start_date" mode="month">'+
+ '<field name="name"/>'+
+ '<field name="start_date"/>'+
+ '</calendar>',
+ archs: archs,
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ mockRPC: function (route, args) {
+ if (args.method === "create") {
+ assert.strictEqual(args.args[0].start_date, "2016-12-20 00:00:00");
+ }
+ if (args.method === "write") {
+ assert.step(args.args[1].start_date);
+ }
+ return this._super(route, args);
+ },
+ session: {
+ getTZOffset: function () {
+ return 120; // 2 hours timezone
+ },
+ },
+ });
+
+ // Create event (on 20 december)
+ var $cell = calendar.$('.fc-day-grid .fc-row:eq(3) .fc-day:eq(2)');
+ testUtils.triggerMouseEvent($cell, "mousedown");
+ testUtils.triggerMouseEvent($cell, "mouseup");
+ await testUtils.nextTick();
+ var $input = $('.modal-body input:first');
+ await testUtils.fields.editInput($input, "An event");
+ await testUtils.dom.click($('.modal button.btn:contains(Create)'));
+ await testUtils.nextTick();
+
+ assert.strictEqual(calendar.$('.o_field_start_date').text().trim(), "12/20/2016")
+
+ // Move event to another day (on 27 november)
+ testUtils.dragAndDrop(
+ calendar.$('.fc-event').first(),
+ calendar.$('.fc-day-top').first()
+ );
+ assert.verifySteps(["2016-11-27 00:00:00"]);
+ assert.strictEqual(calendar.$('.o_field_start_date').text().trim(), "11/27/2016")
+
+ // Move event to last day (on 7 january)
+ await testUtils.dragAndDrop(
+ calendar.$('.fc-event').first(),
+ calendar.$('.fc-day-top').last()
+ );
+ assert.verifySteps(["2016-11-27 00:00:00", "2017-01-07 00:00:00"]);
+ assert.strictEqual(calendar.$('.o_field_start_date').text().trim(), "01/07/2017")
+
+ calendar.destroy();
+ });
+
QUnit.test('form_view_id attribute works (for creating events)', async function (assert) {
assert.expect(1);
diff --git a/addons/web/static/tests/views/kanban_model_tests.js b/addons/web/static/tests/views/kanban_model_tests.js
index e69b08aa3a8..af27cadad88 100644
--- a/addons/web/static/tests/views/kanban_model_tests.js
+++ b/addons/web/static/tests/views/kanban_model_tests.js
@@ -302,6 +302,70 @@ QUnit.module('Views', {
})
});
+
+ QUnit.test('call get (raw: true) before loading x2many data', function (assert) {
+ // Sometimes, get can be called on a datapoint that is currently being
+ // reloaded, and thus in a partially updated state (e.g. in a kanban
+ // view, the user interacts with the searchview, and before the view is
+ // fully reloaded, it clicks on CREATE). Ideally, this shouldn't happen,
+ // but with the sync API of get, we can't change that easily. So at most,
+ // we can ensure that it doesn't crash. Moreover, sensitive functions
+ // requesting the state for more precise information that, e.g., the
+ // count, can do that in the mutex to ensure that the state isn't
+ // currently being reloaded.
+ // In this test, we have a grouped kanban view with a one2many, whose
+ // relational data is loaded in batch, once for all groups. We call get
+ // when the search_read for the first group has returned, but not the
+ // second (and thus, the read of the one2many hasn't started yet).
+ // Note: this test can be removed as soon as search_reads are performed
+ // alongside read_group.
+ assert.expect(2);
+
+ this.data.partner.records[1].product_ids = [37, 41];
+ this.params.fieldsInfo = {
+ kanban: {
+ product_ids: {
+ fieldsInfo: {
+ default: { display_name: {}, color: {} },
+ },
+ relatedFields: this.data.product.fields,
+ viewType: 'default',
+ },
+ },
+ };
+ this.params.viewType = 'kanban';
+ this.params.groupedBy = ['foo'];
+
+ var block;
+ var def = testUtils.makeTestPromise();
+ var model = createModel({
+ Model: KanbanModel,
+ data: this.data,
+ mockRPC: function (route) {
+ var result = this._super.apply(this, arguments);
+ if (route === '/web/dataset/search_read' && block) {
+ block = false;
+ return Promise.all([def]).then(_.constant(result));
+ }
+ return result;
+ },
+ });
+
+ model.load(this.params).then(function (handle) {
+ block = true;
+ model.reload(handle, {});
+
+ var state = model.get(handle, {raw: true});
+ assert.strictEqual(state.count, 2);
+
+ def.resolve();
+
+ state = model.get(handle, {raw: true});
+ assert.strictEqual(state.count, 2);
+ });
+
+ model.destroy();
+ });
});
});
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index f10771a8e66..80e293a5126 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -4683,6 +4683,50 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('readonly boolean in editable list is readonly', function (assert) {
+ assert.expect(6);
+
+ var list = createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="foo"/>' +
+ '<field name="bar" attrs="{\'readonly\': [(\'foo\', \'!=\', \'yop\')]}"/>' +
+ '</tree>',
+ });
+
+ // clicking on disabled checkbox with active row does not work
+ var $disabledCell = list.$('.o_data_row:eq(1) .o_data_cell:last-child');
+ testUtils.dom.click($disabledCell.prev());
+ assert.containsOnce($disabledCell, ':disabled:checked');
+ var $disabledLabel = $disabledCell.find('.custom-control-label');
+ testUtils.dom.click($disabledLabel);
+ assert.containsOnce($disabledCell, ':checked',
+ "clicking disabled checkbox did not work"
+ );
+ assert.ok(
+ $(document.activeElement).is('input[type="text"]'),
+ "disabled checkbox is not focused after click"
+ );
+
+ // clicking on enabled checkbox with active row toggles check mark
+ var $enabledCell = list.$('.o_data_row:eq(0) .o_data_cell:last-child');
+ testUtils.dom.click($enabledCell.prev());
+ assert.containsOnce($enabledCell, ':checked:not(:disabled)');
+ var $enabledLabel = $enabledCell.find('.custom-control-label');
+ testUtils.dom.click($enabledLabel);
+ assert.containsNone($enabledCell, ':checked',
+ "clicking enabled checkbox worked and unchecked it"
+ );
+ assert.ok(
+ $(document.activeElement).is('input[type="checkbox"]'),
+ "enabled checkbox is focused after click"
+ );
+
+ list.destroy();
+ });
+
QUnit.test('grouped list with async widget', async function (assert) {
assert.expect(4);
diff --git a/addons/web/static/tests/views/search_view_tests.js b/addons/web/static/tests/views/search_view_tests.js
index 5980ab9211b..64c7f351e6a 100644
--- a/addons/web/static/tests/views/search_view_tests.js
+++ b/addons/web/static/tests/views/search_view_tests.js
@@ -732,6 +732,37 @@ QUnit.module('Search View', {
actionManager.destroy();
});
+ QUnit.test('Filter with JSON-parsable domain works', function (assert) {
+ assert.expect(1);
+
+ var domain = [['foo' ,'=', 'Gently Weeps']];
+ var xml_domain = JSON.stringify(domain);
+
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/search_read') {
+ assert.deepEqual(args.domain, domain,
+ 'A JSON parsable xml domain should be handled just like any other');
+ }
+ return this._super.apply(this, arguments);
+ }
+ });
+
+ this.archs['partner,5,search'] =
+ '<search>'+
+ '<filter string="Foo" name="gently_weeps" domain="' + _.escape(xml_domain) + '" />' +
+ '</search>';
+ this.actions[0].search_view_id = [5, 'search'];
+ this.actions[0].context = {search_default_gently_weeps: true};
+
+ actionManager.doAction(1);
+
+ actionManager.destroy();
+ });
+
QUnit.module('Favorites Menu');
QUnit.test('dynamic filters are saved dynamic', async function (assert) {
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 506646d4697..dc2a444384b 100644
--- a/addons/web_editor/static/src/js/wysiwyg/plugin/bullet.js
+++ b/addons/web_editor/static/src/js/wysiwyg/plugin/bullet.js
@@ -173,6 +173,7 @@ var BulletPlugin = AbstractPlugin.extend({
} else {
ul = this.document.createElement(sorted === "ol" ? "ol" : "ul");
ul.className = ol.className;
+ $(ul).removeClass('o_checklist');
$(ul).insertBefore(ol).append(lis);
parent.removeChild(ol);
}
@@ -312,6 +313,10 @@ var BulletPlugin = AbstractPlugin.extend({
if (!range) {
return;
}
+ // list groups shouldn't be outdented like regular lists
+ if ($(dom.ancestor(range.sc, dom.isList)).hasClass('list-group')) {
+ return;
+ }
var self = this;
var nodes = [];
diff --git a/addons/web_editor/static/src/js/wysiwyg/plugin/helper.js b/addons/web_editor/static/src/js/wysiwyg/plugin/helper.js
index 4ba44fe3c9b..521afe6137a 100644
--- a/addons/web_editor/static/src/js/wysiwyg/plugin/helper.js
+++ b/addons/web_editor/static/src/js/wysiwyg/plugin/helper.js
@@ -109,12 +109,14 @@ var HelperPlugin = AbstractPlugin.extend({
this.orderStyle(node);
this.orderClass(otherNode);
this.orderStyle(otherNode);
- if (node.attributes.length !== otherNode.attributes.length) {
+ var nodeAttributes = this._getAttrsNoID(node);
+ var otherNodeAttributes = this._getAttrsNoID(otherNode);
+ if (nodeAttributes.length !== otherNodeAttributes.length) {
return false;
}
- for (var i = 0; i < node.attributes.length; i++) {
- var attr = node.attributes[i];
- var otherAttr = otherNode.attributes[i];
+ for (var i = 0; i < nodeAttributes.length; i++) {
+ var attr = nodeAttributes[i];
+ var otherAttr = otherNodeAttributes[i];
if (attr.name !== otherAttr.name || attr.value !== otherAttr.value) {
return false;
}
@@ -306,6 +308,7 @@ var HelperPlugin = AbstractPlugin.extend({
var range = this.context.invoke('editor.createRange');
var spaceToRemove = [];
+ var offset;
while ((node = nodes.pop())) {
next = node[prevOrNext];
while (next && !next.tagName) {
@@ -316,6 +319,31 @@ var HelperPlugin = AbstractPlugin.extend({
}
break;
}
+
+ // handle merge of text with p (useful in case of <li>text</li> with <li><p>text</p></li>)
+ if (next && dom.isText(node) && this.isNodeBlockType(next)) {
+ node = [next, next = node][0]; // swap node and next
+ direction = direction === 'next' ? 'prev' : 'next'; // switch direction
+ }
+ if (next && dom.isText(next) && this.isNodeBlockType(node)) {
+ if (this.isBlankNode(node, true)) {
+ $(node).empty();
+ }
+ $(node)[direction === 'next' ? 'append' : 'prepend'](next);
+ offset = direction === 'next' ? 0 : dom.nodeLength(next);
+ if (dom.isText(next[prevOrNext])) {
+ // merge text if needed
+ var sib = next[prevOrNext];
+ var firstText = (direction === 'next' ? sib : next).textContent;
+ var secondText = (direction === 'next' ? next : sib).textContent;
+ next.textContent = firstText + secondText;
+ $(sib).remove();
+ offset = firstText.length;
+ }
+ result = this.makePoint(next, offset);
+ break;
+ }
+
if (
!next ||
!(node.tagName || next.tagName === 'BR') ||
@@ -328,7 +356,7 @@ var HelperPlugin = AbstractPlugin.extend({
var newNext = next[prevOrNext];
$(next).remove();
next = newNext;
- var offset = (next ? direction === 'prev' : direction === 'next') ? dom.nodeLength(next) : 0;
+ offset = (next ? direction === 'prev' : direction === 'next') ? dom.nodeLength(next) : 0;
result = this.makePoint(next || node, offset);
if (!ifBrRemovedAndMerge) {
continue;
@@ -455,6 +483,11 @@ var HelperPlugin = AbstractPlugin.extend({
return this.makePoint(this.firstLeaf(blockToMergeFrom), 0);
}
+ // UL/OL can only have LI, UL or OR as direct children
+ if (dom.isList(blockToMergeInto) && !dom.isLi(blockToMergeFrom) && !dom.isList(blockToMergeFrom)) {
+ return;
+ }
+
return this.mergeNonSimilarBlocks(blockToMergeFrom, blockToMergeInto);
},
/**
@@ -601,22 +634,24 @@ var HelperPlugin = AbstractPlugin.extend({
var li = dom.ancestor(node, function (n) {
return n !== node && self.isNodeBlockType(n) || dom.isLi(n);
});
+ var nextElementSibling = direction === 'next' ? 'nextElementSibling' : 'previousElementSibling';
li = li && dom.isLi(li) ? li : undefined;
- if (li && direction === 'next') {
- if (li.nextElementSibling) {
+ if (li) {
+ if (li[nextElementSibling]) {
node = li;
} else {
node = dom.ancestor(node, function (n) {
- return ((n.tagName === 'UL' || n.tagName === 'OL') && n.nextElementSibling);
+ return ((n.tagName === 'UL' || n.tagName === 'OL') && n[nextElementSibling] &&
+ (direction === 'next' || dom.isList(n[nextElementSibling])));
});
}
}
- if (!node || !node[direction === 'next' ? 'nextElementSibling' : 'previousElementSibling']) {
+ if (!node || !node[nextElementSibling]) {
return false;
}
- node = node[direction === 'next' ? 'nextElementSibling' : 'previousElementSibling'];
+ node = node[nextElementSibling];
var ulFoldedSnippetNode = dom.ancestor(node, function (n) {
return $(n).hasClass('o_ul_folded');
@@ -633,7 +668,7 @@ var HelperPlugin = AbstractPlugin.extend({
node = this.firstBlockAncestor(node);
- li = dom.ancestor(node, function (n) {
+ li = dom.isList(node) ? undefined : dom.ancestor(node, function (n) {
return n !== node && self.isNodeBlockType(n) || dom.isLi(n);
});
li = li && dom.isLi(li) ? li : undefined;
@@ -1051,9 +1086,15 @@ var HelperPlugin = AbstractPlugin.extend({
* but without any.
*
* @param {Node} node
+ * @param {Boolean} brIsBlank true to consider nodes with BR element
+ * as ultimate only child to be blank
* @returns {Boolean}
*/
- isBlankNode: function (node) {
+ isBlankNode: function (node, brIsBlank) {
+ var self = this;
+ if (brIsBlank && dom.isBR(node)) {
+ return true;
+ }
if (dom.isVoid(node) || dom.isIcon(node)) {
return false;
}
@@ -1062,7 +1103,10 @@ var HelperPlugin = AbstractPlugin.extend({
}).test(node[dom.isText(node) ? 'textContent' : 'innerHTML'])) {
return true;
}
- if (node.childNodes.length && _.all(node.childNodes, this.isBlankNode.bind(this))) {
+ var areAllChildrenBlank = _.all(node.childNodes, function (node) {
+ return self.isBlankNode(node, brIsBlank);
+ });
+ if (node.childNodes.length && areAllChildrenBlank) {
return true;
}
return false;
@@ -1306,7 +1350,7 @@ var HelperPlugin = AbstractPlugin.extend({
$lastContents.after($contents);
}
}
- while (mergeFromBlock.parentNode && this.isBlankNode(mergeFromBlock.parentNode)) {
+ while (mergeFromBlock.parentNode && this.isBlankNode(mergeFromBlock.parentNode, true)) {
mergeFromBlock = mergeFromBlock.parentNode;
}
$(mergeFromBlock).remove();
@@ -1478,7 +1522,7 @@ var HelperPlugin = AbstractPlugin.extend({
if (isAfterBlank) {
$(node.previousSibling).remove();
}
- var isBeforeBlank = node.nextSibling && this.isBlankNode(node.nextSibling)
+ var isBeforeBlank = node.nextSibling && this.isBlankNode(node.nextSibling);
if (isBeforeBlank) {
$(node.nextSibling).remove();
}
@@ -1733,6 +1777,15 @@ var HelperPlugin = AbstractPlugin.extend({
// Private
//--------------------------------------------------------------------------
+ _getAttrsNoID: function (node) {
+ var nodeAttributes = [];
+ for (var i = 0; i < node.attributes.length; i++) {
+ if (node.attributes[i].name !== 'id') {
+ nodeAttributes.push(node.attributes[i]);
+ }
+ }
+ return nodeAttributes;
+ },
_insertTextNodeInEditableArea: function (range, text) {
// try to insert the text node in editable area
var textNode = this.document.createTextNode(text);
diff --git a/addons/web_editor/static/src/js/wysiwyg/plugin/keyboard.js b/addons/web_editor/static/src/js/wysiwyg/plugin/keyboard.js
index 3ab6a5f0317..94ec9306f7d 100644
--- a/addons/web_editor/static/src/js/wysiwyg/plugin/keyboard.js
+++ b/addons/web_editor/static/src/js/wysiwyg/plugin/keyboard.js
@@ -296,6 +296,7 @@ var KeyboardPlugin = AbstractPlugin.extend({
var next = this.context.invoke('HelperPlugin.splitTree', ancestor, point, {
isSkipPaddingBlankHTML: !this.context.invoke('HelperPlugin.isNodeBlockType', point.node.parentNode) && !!point.node.parentNode.nextSibling
});
+ $(next).removeClass('o_checked');
while (next.firstChild) {
next = next.firstChild;
}
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 d3ac908c507..16e22939cd4 100644
--- a/addons/web_editor/static/src/js/wysiwyg/plugin/text.js
+++ b/addons/web_editor/static/src/js/wysiwyg/plugin/text.js
@@ -85,7 +85,7 @@ var TextPlugin = AbstractPlugin.extend({
return;
}
nodes = textOnly ? this.document.createTextNode($(nodes).text()) : nodes;
- nodes = this._mergeAdjacentULs(nodes);
+ nodes = textOnly ? nodes : this._mergeAdjacentULs(nodes);
var point = this._getPastePoint();
// Prevent pasting HTML within a link:
diff --git a/addons/web_editor/static/src/js/wysiwyg_snippets/snippets.editor.js b/addons/web_editor/static/src/js/wysiwyg_snippets/snippets.editor.js
index 63692848eb1..9bc9124688b 100644
--- a/addons/web_editor/static/src/js/wysiwyg_snippets/snippets.editor.js
+++ b/addons/web_editor/static/src/js/wysiwyg_snippets/snippets.editor.js
@@ -918,7 +918,7 @@ var SnippetsMenu = Widget.extend({
&& (float_next === 'left' || float_next === 'right')) {
zone.remove();
} else if (disp_prev !== null && disp_next !== null
- && disp_prev !== 'block' && disp_next !== 'block') {
+ && disp_prev.indexOf('inline') >= 0 && disp_next.indexOf('inline') >= 0) {
zone.remove();
}
});
diff --git a/addons/web_editor/static/src/scss/bootstrap_overridden.scss b/addons/web_editor/static/src/scss/bootstrap_overridden.scss
index d1644a1174e..4517852076f 100644
--- a/addons/web_editor/static/src/scss/bootstrap_overridden.scss
+++ b/addons/web_editor/static/src/scss/bootstrap_overridden.scss
@@ -55,3 +55,5 @@ $gray-700: gray('700') !default;
$gray-800: gray('800') !default;
$gray-900: gray('900') !default;
$black: gray('black') !default;
+
+$font-family-sans-serif: "Noto", "Lucida Grande", Helvetica, Verdana, Arial, sans-serif;
diff --git a/addons/web_editor/static/tests/wysiwyg_keyboard_tests.js b/addons/web_editor/static/tests/wysiwyg_keyboard_tests.js
index 6a4e456cd00..6c08fec0d98 100644
--- a/addons/web_editor/static/tests/wysiwyg_keyboard_tests.js
+++ b/addons/web_editor/static/tests/wysiwyg_keyboard_tests.js
@@ -1185,6 +1185,30 @@ var keyboardTestsEnter = [{
start: "br->0",
},
},
+ {
+ name: "in ul.o_checklist > li.o_checked > p: ENTER at end",
+ content: '<ul class="o_checklist"><li class="o_checked"><p>test</p></li></ul>',
+ steps: [{
+ start: "p:contents()[0]->4",
+ key: 'ENTER',
+ }],
+ test: {
+ content: '<ul class="o_checklist"><li class="o_checked"><p>test</p></li><li><p><br></p></li></ul>',
+ start: "br->0",
+ },
+ },
+ {
+ name: "in ul.o_checklist > li.o_checked > p > b: ENTER within text",
+ content: '<ul class="o_checklist"><li class="o_checked"><p><b>test</b></p></li></ul>',
+ steps: [{
+ start: "b:contents()[0]->2",
+ key: 'ENTER',
+ }],
+ test: {
+ content: '<ul class="o_checklist"><li class="o_checked"><p><b>te</b></p></li><li><p><b>st</b></p></li></ul>',
+ start: "b:eq(1):contents()[0]->0",
+ },
+ },
// end list UL / OL
@@ -2088,6 +2112,142 @@ var keyboardTestsComplex = [{
start: "h1:contents()[0]->15",
},
},
+ {
+ name: "in ul > li > ul > 1st li > empty p (other ul li ul before): BACKSPACE at start",
+ content: '<ul><li><p>1</p></li><li class="o_indent"><ul><li><p>2</p></li></ul><ul class="o_checklist"><li id="checklist-id-1"><p><br></p></li></ul></li><li><p>3</p></li></ul>',
+ steps: [{
+ start: "p:eq(2)->0",
+ key: 'BACKSPACE',
+ }],
+ test: {
+ content: '<ul><li><p>1</p></li><li class="o_indent"><ul><li><p>2</p></li></ul></li><li><p>3</p></li></ul>',
+ start: "p:eq(1):contents()[0]->1",
+ },
+ },
+ {
+ name: "in ul > li > p > text (li > text before): BACKSPACE at start",
+ content: '<ul><li>1</li><li>2</li><li><p><br></p></li><li>3</li></ul>',
+ steps: [{
+ start: "p:contents()[0]->0",
+ key: 'BACKSPACE',
+ }],
+ test: {
+ content: '<ul><li>1</li><li><p>2</p></li><li>3</li></ul>',
+ start: "p:contents()[0]->1",
+ },
+ },
+ {
+ name: "in ul > li > p > text (li > text before): 3x BACKSPACE after first character",
+ content: '<ul><li>1</li><li>2</li><li><p>34</p></li><li>5</li></ul>',
+ steps: [{
+ start: "p:contents()[0]->1",
+ key: 'BACKSPACE',
+ }, {
+ key: 'BACKSPACE',
+ }, {
+ key: 'BACKSPACE',
+ }],
+ test: {
+ content: '<ul><li>1</li><li><p>4</p></li><li>5</li></ul>',
+ start: "p:contents()[0]->0",
+ },
+ },
+ {
+ name: "in ul > li > text (li > p > text before): BACKSPACE at start",
+ content: '<ul><li>1</li><li><p>2</p></li><li>3</li><li>4</li></ul>',
+ steps: [{
+ start: "li:eq(2):contents()[0]->0",
+ key: 'BACKSPACE',
+ }],
+ test: {
+ content: '<ul><li>1</li><li><p>23</p></li><li>4</li></ul>',
+ start: "p:contents()[0]->1",
+ },
+ },
+ {
+ name: "in ul > li > text (li > p > text before): 3x BACKSPACE after first character",
+ content: '<ul><li>1</li><li><p>2</p></li><li>34</li><li>5</li></ul>',
+ steps: [{
+ start: "li:eq(2):contents()[0]->1",
+ key: 'BACKSPACE',
+ }, {
+ key: 'BACKSPACE',
+ }, {
+ key: 'BACKSPACE',
+ }],
+ test: {
+ content: '<ul><li>1</li><li><p>4</p></li><li>5</li></ul>',
+ start: "p:contents()[0]->0",
+ },
+ },
+ {
+ name: "in ul > li > p > text (li > text after): DELETE at end",
+ content: '<ul><li>1</li><li><p><br></p></li><li>2</li><li>3</li></ul>',
+ steps: [{
+ start: "p->0",
+ key: 'DELETE',
+ }],
+ test: {
+ content: '<ul><li>1</li><li><p>2</p></li><li>3</li></ul>',
+ start: "p:contents()[0]->0",
+ },
+ },
+ {
+ name: "in ul > li > p > text (li > text after): 3x DELETE before last character",
+ content: '<ul><li>1</li><li><p>23</p></li><li>4</li><li>5</li></ul>',
+ steps: [{
+ start: "p:contents()[0]->1",
+ key: 'DELETE',
+ }, {
+ key: 'DELETE',
+ }, {
+ key: 'DELETE',
+ }],
+ test: {
+ content: '<ul><li>1</li><li><p>2</p></li><li>5</li></ul>',
+ start: "p:contents()[0]->1",
+ },
+ },
+ {
+ name: "in ul > li > text (li > p > text after): DELETE at end",
+ content: '<ul><li>1</li><li>2</li><li><p>3</p></li><li>4</li></ul>',
+ steps: [{
+ start: "li:eq(1):contents()[0]->1",
+ key: 'DELETE',
+ }],
+ test: {
+ content: '<ul><li>1</li><li><p>23</p></li><li>4</li></ul>',
+ start: "p:contents()[0]->1",
+ },
+ },
+ {
+ name: "in ul > li > text (li > p > text after): 3x DELETE before last character",
+ content: '<ul><li>1</li><li>23</li><li><p>4</p></li><li>5</li></ul>',
+ steps: [{
+ start: "li:eq(1):contents()[0]->1",
+ key: 'DELETE',
+ }, {
+ key: 'DELETE',
+ }, {
+ key: 'DELETE',
+ }],
+ test: {
+ content: '<ul><li>1</li><li><p>2</p></li><li>5</li></ul>',
+ start: "p:contents()[0]->1",
+ },
+ },
+ {
+ name: "in ul.list-group (div > p > text before): BACKSPACE at start",
+ content: '<div><p>1</p></div><ul class="list-group list-group-flush"><li class="list-group-item"><p><br></p></li></ul>',
+ steps: [{
+ start: "p:eq(1)->0",
+ key: 'BACKSPACE',
+ }],
+ test: {
+ content: '<div><p>1</p></div><ul class="list-group list-group-flush"><li class="list-group-item"><p><br></p></li></ul>',
+ start: "p:eq(1)->0",
+ },
+ },
];
QUnit.test('Complex', function (assert) {
diff --git a/addons/web_editor/static/tests/wysiwyg_tests.js b/addons/web_editor/static/tests/wysiwyg_tests.js
index bb95a6776d4..7462f5a330b 100644
--- a/addons/web_editor/static/tests/wysiwyg_tests.js
+++ b/addons/web_editor/static/tests/wysiwyg_tests.js
@@ -1141,7 +1141,7 @@ QUnit.test('Text bgcolor', function (assert) {
});
QUnit.test('Unordered list', function (assert) {
- assert.expect(10);
+ assert.expect(34);
return weTestUtils.createWysiwyg({
debug: false,
@@ -1223,6 +1223,176 @@ QUnit.test('Unordered list', function (assert) {
end: 'p:eq(2):contents()[0]->5',
},
},
+ // Conversion from OL
+ {
+ name: "Click UL: ol -> ul",
+ content: '<p>dom not to edit</p><ol><li><p>dom to edit</p></li></ol>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(1):contents()[0]->5',
+ do: function () {
+ $btnUL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul><li><p>dom to edit</p></li></ul>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(1):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click UL: ol -> ul (across li's)",
+ content: '<p>dom not to edit</p><ol><li><p>dom to edit</p></li><li><p>dom to edit</p></li></ol>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ do: function () {
+ $btnUL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul><li><p>dom to edit</p></li><li><p>dom to edit</p></li></ul>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click UL: ol -> ul (from second li)",
+ content: '<p>dom not to edit</p><ol><li><p>xxx</p></li><li><p>dom to edit</p></li></ol>',
+ start: 'li:eq(1) p:contents()[0]->1',
+ end: 'li:eq(1) p:contents()[0]->5',
+ do: function () {
+ $btnUL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ol><li><p>xxx</p></li></ol><ul><li><p>dom to edit</p></li></ul>',
+ start: 'p:eq(2):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click UL: ul ol -> ul ul (from indented li)",
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ol><li><p>dom to edit</p></li></ol></ul>',
+ start: 'li:eq(1) p:contents()[0]->1',
+ end: 'li:eq(1) p:contents()[0]->5',
+ do: function () {
+ $btnUL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ul><li><p>dom to edit</p></li></ul></ul>',
+ start: 'p:eq(2):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click UL: ul ol -> ul ul (across several indented li)",
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ol><li><p>dom to edit 1</p></li><li><p>dom to edit 2</p></li></ol></ul>',
+ start: 'li:eq(1) p:contents()[0]->1',
+ end: 'li:eq(2) p:contents()[0]->5',
+ do: function () {
+ $btnUL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ul><li><p>dom to edit 1</p></li><li><p>dom to edit 2</p></li></ul></ul>',
+ start: 'p:eq(2):contents()[0]->1',
+ end: 'p:eq(3):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click UL: ul ol -> ul ul (from second indented li)",
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ol><li><p>dom not to edit</p></li><li><p>dom to edit</p></li><li><p>dom not to edit</p></li></ol></ul>',
+ start: 'li:eq(2) p:contents()[0]->1',
+ end: 'li:eq(2) p:contents()[0]->5',
+ do: function () {
+ $btnUL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ol><li><p>dom not to edit</p></li></ol><ul><li><p>dom to edit</p></li></ul><ol><li><p>dom not to edit</p></li></ol></ul>',
+ start: 'p:eq(3):contents()[0]->1',
+ end: 'p:eq(3):contents()[0]->5',
+ },
+ },
+ // Conversion from Checklist
+ {
+ name: "Click UL: ul.o_checklist -> ul",
+ content: '<p>dom not to edit</p><ul class="o_checklist"><li><p>dom to edit</p></li></ul>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(1):contents()[0]->5',
+ do: function () {
+ $btnUL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul><li><p>dom to edit</p></li></ul>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(1):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click UL: ul.o_checklist -> ul (across li's)",
+ content: '<p>dom not to edit</p><ul class="o_checklist"><li><p>dom to edit</p></li><li><p>dom to edit</p></li></ul>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ do: function () {
+ $btnUL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul><li><p>dom to edit</p></li><li><p>dom to edit</p></li></ul>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click UL: ul.o_checklist -> ul (from second li)",
+ content: '<p>dom not to edit</p><ul class="o_checklist"><li><p>xxx</p></li><li><p>dom to edit</p></li></ul>',
+ start: 'li:eq(1) p:contents()[0]->1',
+ end: 'li:eq(1) p:contents()[0]->5',
+ do: function () {
+ $btnUL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul class="o_checklist"><li><p>xxx</p></li></ul><ul><li><p>dom to edit</p></li></ul>',
+ start: 'p:eq(2):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click UL: ul ul.o_checklist -> ul ul (from indented li)",
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ul class="o_checklist"><li><p>dom to edit</p></li></ul></ul>',
+ start: 'li:eq(1) p:contents()[0]->1',
+ end: 'li:eq(1) p:contents()[0]->5',
+ do: function () {
+ $btnUL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ul><li><p>dom to edit</p></li></ul></ul>',
+ start: 'p:eq(2):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click UL: ul ul.o_checklist -> ul ul (across several indented li)",
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ul class="o_checklist"><li><p>dom to edit 1</p></li><li><p>dom to edit 2</p></li></ul></ul>',
+ start: 'li:eq(1) p:contents()[0]->1',
+ end: 'li:eq(2) p:contents()[0]->5',
+ do: function () {
+ $btnUL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ul><li><p>dom to edit 1</p></li><li><p>dom to edit 2</p></li></ul></ul>',
+ start: 'p:eq(2):contents()[0]->1',
+ end: 'p:eq(3):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click UL: ul ul.o_checklist -> ul ul (from second indented li)",
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ul class="o_checklist"><li><p>dom not to edit</p></li><li><p>dom to edit</p></li><li><p>dom not to edit</p></li></ul></ul>',
+ start: 'li:eq(2) p:contents()[0]->1',
+ end: 'li:eq(2) p:contents()[0]->5',
+ do: function () {
+ $btnUL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ul class="o_checklist"><li><p>dom not to edit</p></li></ul><ul><li><p>dom to edit</p></li></ul><ul class="o_checklist"><li><p>dom not to edit</p></li></ul></ul>',
+ start: 'p:eq(3):contents()[0]->1',
+ end: 'p:eq(3):contents()[0]->5',
+ },
+ },
];
var def = Promise.resolve();
@@ -1244,7 +1414,7 @@ QUnit.test('Unordered list', function (assert) {
});
QUnit.test('Ordered list', function (assert) {
- assert.expect(32);
+ assert.expect(56);
return weTestUtils.createWysiwyg({
debug: false,
@@ -1675,6 +1845,176 @@ QUnit.test('Ordered list', function (assert) {
start: 'font:contents(0)->11',
},
},
+ // Conversion from UL
+ {
+ name: "Click OL: ul -> ol",
+ content: '<p>dom not to edit</p><ul><li><p>dom to edit</p></li></ul>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(1):contents()[0]->5',
+ do: function () {
+ $btnOL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ol><li><p>dom to edit</p></li></ol>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(1):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click OL: ul -> ol (across li's)",
+ content: '<p>dom not to edit</p><ul><li><p>dom to edit</p></li><li><p>dom to edit</p></li></ul>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ do: function () {
+ $btnOL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ol><li><p>dom to edit</p></li><li><p>dom to edit</p></li></ol>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click OL: ul -> ol (from second li)",
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><li><p>dom to edit</p></li></ul>',
+ start: 'li:eq(1) p:contents()[0]->1',
+ end: 'li:eq(1) p:contents()[0]->5',
+ do: function () {
+ $btnOL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li></ul><ol><li><p>dom to edit</p></li></ol>',
+ start: 'p:eq(2):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click OL: ol ul -> ol ol (from indented li)",
+ content: '<p>dom not to edit</p><ol><li><p>xxx</p></li><ul><li><p>dom to edit</p></li></ul></ol>',
+ start: 'li:eq(1) p:contents()[0]->1',
+ end: 'li:eq(1) p:contents()[0]->5',
+ do: function () {
+ $btnOL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ol><li><p>xxx</p></li><ol><li><p>dom to edit</p></li></ol></ol>',
+ start: 'p:eq(2):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click OL: ol ul -> ol ol (across several indented li)",
+ content: '<p>dom not to edit</p><ol><li><p>xxx</p></li><ul><li><p>dom to edit 1</p></li><li><p>dom to edit 2</p></li></ul></ol>',
+ start: 'li:eq(1) p:contents()[0]->1',
+ end: 'li:eq(2) p:contents()[0]->5',
+ do: function () {
+ $btnOL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ol><li><p>xxx</p></li><ol><li><p>dom to edit 1</p></li><li><p>dom to edit 2</p></li></ol></ol>',
+ start: 'p:eq(2):contents()[0]->1',
+ end: 'p:eq(3):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click OL: ol ul -> ol ol (from second indented li)",
+ content: '<p>dom not to edit</p><ol><li><p>xxx</p></li><ul><li><p>dom not to edit</p></li><li><p>dom to edit</p></li><li><p>dom not to edit</p></li></ul></ol>',
+ start: 'li:eq(2) p:contents()[0]->1',
+ end: 'li:eq(2) p:contents()[0]->5',
+ do: function () {
+ $btnOL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ol><li><p>xxx</p></li><ul><li><p>dom not to edit</p></li></ul><ol><li><p>dom to edit</p></li></ol><ul><li><p>dom not to edit</p></li></ul></ol>',
+ start: 'p:eq(3):contents()[0]->1',
+ end: 'p:eq(3):contents()[0]->5',
+ },
+ },
+ // Conversion from Checklist
+ {
+ name: "Click OL: ul.o_checklist -> ol",
+ content: '<p>dom not to edit</p><ul class="o_checklist"><li><p>dom to edit</p></li></ul>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(1):contents()[0]->5',
+ do: function () {
+ $btnOL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ol><li><p>dom to edit</p></li></ol>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(1):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click OL: ul.o_checklist -> ol (across li's)",
+ content: '<p>dom not to edit</p><ul class="o_checklist"><li><p>dom to edit</p></li><li><p>dom to edit</p></li></ul>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ do: function () {
+ $btnOL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ol><li><p>dom to edit</p></li><li><p>dom to edit</p></li></ol>',
+ start: 'p:eq(1):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click OL: ul.o_checklist -> ol (from second li)",
+ content: '<p>dom not to edit</p><ul class="o_checklist"><li><p>xxx</p></li><li><p>dom to edit</p></li></ul>',
+ start: 'li:eq(1) p:contents()[0]->1',
+ end: 'li:eq(1) p:contents()[0]->5',
+ do: function () {
+ $btnOL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul class="o_checklist"><li><p>xxx</p></li></ul><ol><li><p>dom to edit</p></li></ol>',
+ start: 'p:eq(2):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click OL: ol ul.o_checklist -> ol ol (from indented li)",
+ content: '<p>dom not to edit</p><ol><li><p>xxx</p></li><ul class="o_checklist"><li><p>dom to edit</p></li></ul></ol>',
+ start: 'li:eq(1) p:contents()[0]->1',
+ end: 'li:eq(1) p:contents()[0]->5',
+ do: function () {
+ $btnOL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ol><li><p>xxx</p></li><ol><li><p>dom to edit</p></li></ol></ol>',
+ start: 'p:eq(2):contents()[0]->1',
+ end: 'p:eq(2):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click OL: ul ul.o_checklist -> ul ol (across several indented li)",
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ul class="o_checklist"><li><p>dom to edit 1</p></li><li><p>dom to edit 2</p></li></ul></ul>',
+ start: 'li:eq(1) p:contents()[0]->1',
+ end: 'li:eq(2) p:contents()[0]->5',
+ do: function () {
+ $btnOL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ol><li><p>dom to edit 1</p></li><li><p>dom to edit 2</p></li></ol></ul>',
+ start: 'p:eq(2):contents()[0]->1',
+ end: 'p:eq(3):contents()[0]->5',
+ },
+ },
+ {
+ name: "Click OL: ul ul.o_checklist -> ul ol (from second indented li)",
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ul class="o_checklist"><li><p>dom not to edit</p></li><li><p>dom to edit</p></li><li><p>dom not to edit</p></li></ul></ul>',
+ start: 'li:eq(2) p:contents()[0]->1',
+ end: 'li:eq(2) p:contents()[0]->5',
+ do: function () {
+ $btnOL.mousedown().click();
+ },
+ test: {
+ content: '<p>dom not to edit</p><ul><li><p>xxx</p></li><ul class="o_checklist"><li><p>dom not to edit</p></li></ul><ol><li><p>dom to edit</p></li></ol><ul class="o_checklist"><li><p>dom not to edit</p></li></ul></ul>',
+ start: 'p:eq(3):contents()[0]->1',
+ end: 'p:eq(3):contents()[0]->5',
+ },
+ },
];
@@ -2116,16 +2456,20 @@ QUnit.test('Indent/outdent', function (assert) {
});
QUnit.test('checklist', function (assert) {
- assert.expect(10);
+ assert.expect(11);
return weTestUtils.createWysiwyg({
debug: false,
wysiwygOptions: {
-
+ generateOptions: function (options) {
+ options.toolbar[4][1] = ['ul', 'ol', 'checklist', 'paragraph'];
+ },
},
}).then(function (wysiwyg) {
var $editable = wysiwyg.$('.note-editable');
+ var $btnChecklist = wysiwyg.$('.note-para .fa-check-square');
+
var checklistTests = [
{
name: "check checkbox in checklist with children",
@@ -2477,6 +2821,60 @@ QUnit.test('checklist', function (assert) {
'<p>y</p>',
},
},
+ {
+ name: "convert 2 ul li ul li into two ul li ul.o_checklist li",
+ content: '<ul>' +
+ '<li>' +
+ '<p>1</p>' +
+ '</li>' +
+ '<li class="o_indent">' +
+ '<ul>' +
+ '<li>' +
+ '<p>2</p>' +
+ '</li>' +
+ '<li>' +
+ '<p>3</p>' +
+ '</li>' +
+ '<li>' +
+ '<p>4</p>' +
+ '</li>' +
+ '</ul>' +
+ '</li>' +
+ '<li>' +
+ '<p>5</p>' +
+ '</li>' +
+ '</ul>',
+ start: 'p:eq(2):contents()[0]->0',
+ end: 'p:eq(3):contents()[0]->1',
+ do: function () {
+ $btnChecklist.mousedown().click();
+ },
+ test: {
+ content: '<ul>' +
+ '<li>' +
+ '<p>1</p>' +
+ '</li>' +
+ '<li class="o_indent">' +
+ '<ul>' +
+ '<li>' +
+ '<p>2</p>' +
+ '</li>' +
+ '</ul>' +
+ '<ul class="o_checklist">' +
+ '<li>' +
+ '<p>3</p>' +
+ '</li>' +
+ '<li>' +
+ '<p>4</p>' +
+ '</li>' +
+ '</ul>' +
+ '</li>' +
+ '<li>' +
+ '<p>5</p>' +
+ '</li>' +
+ '</ul>',
+ },
+ },
];
var def = Promise.resolve();
@@ -2485,6 +2883,11 @@ QUnit.test('checklist', function (assert) {
def = def.then(async function(){
testName = test.name;
wysiwyg.setValue(test.content);
+ if (test.start) {
+ var range = weTestUtils.select(test.start, test.end || test.start, $editable);
+ $(range.sc).mousedown();
+ Wysiwyg.setRange(range.sc, range.so, range.ec, range.eo);
+ }
await test.do();
assert.deepEqual(wysiwyg.getValue(), test.test.content, testName);
});
diff --git a/addons/web_unsplash/static/src/js/unsplash_image_widget.js b/addons/web_unsplash/static/src/js/unsplash_image_widget.js
index 01785fc12d9..3c0d1964513 100644
--- a/addons/web_unsplash/static/src/js/unsplash_image_widget.js
+++ b/addons/web_unsplash/static/src/js/unsplash_image_widget.js
@@ -229,7 +229,8 @@ widgetsMedia.ImageWidget.include({
params: params,
}).then(function () {
self.unsplashAPI.clientId = key;
- self._renderImages();
+ self._unsplash.error = false;
+ self.search(self._unsplash.query);
});
}
}
diff --git a/addons/website/models/res_users.py b/addons/website/models/res_users.py
index 6338a656d2c..a84eaaeb3c6 100644
--- a/addons/website/models/res_users.py
+++ b/addons/website/models/res_users.py
@@ -35,10 +35,12 @@ class ResUsers(models.Model):
@api.model
def _signup_create_user(self, values):
- new_user = super(ResUsers, self)._signup_create_user(values)
current_website = self.env['website'].get_current_website()
if request and current_website.specific_user_account:
- new_user.website_id = current_website
+ values['company_id'] = current_website.company_id.id
+ values['company_ids'] = [(4, current_website.company_id.id)]
+ values['website_id'] = current_website.id
+ new_user = super(ResUsers, self)._signup_create_user(values)
return new_user
@api.model
diff --git a/addons/website/static/src/js/content/snippets.animation.js b/addons/website/static/src/js/content/snippets.animation.js
index 8f0c42d2873..7f57eb81f2c 100644
--- a/addons/website/static/src/js/content/snippets.animation.js
+++ b/addons/website/static/src/js/content/snippets.animation.js
@@ -965,7 +965,7 @@ registry.facebookPage = publicWidget.Widget.extend({
if (!params.href) {
return def;
}
- params.width = utils.confine(this.$el.width(), 180, 500);
+ params.width = utils.confine(Math.floor(this.$el.width()), 180, 500);
var src = $.param.querystring('https://www.facebook.com/plugins/page.php', params);
this.$iframe = $('<iframe/>', {
diff --git a/addons/website/static/src/scss/primary_variables.scss b/addons/website/static/src/scss/primary_variables.scss
index f7fed95fe07..bb9d182756a 100644
--- a/addons/website/static/src/scss/primary_variables.scss
+++ b/addons/website/static/src/scss/primary_variables.scss
@@ -64,7 +64,7 @@ $o-user-website-values: () !default;
//------------------------------------------------------------------------------
$o-theme-fonts: (
- (-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"), // This is BS default
+ (-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Noto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"), // This is BS default
('Open Sans', sans-serif),
('Source Sans Pro', sans-serif),
('Raleway', sans-serif),
diff --git a/addons/website/views/website_navbar_templates.xml b/addons/website/views/website_navbar_templates.xml
index 08ef7b54ab4..f631e56e08c 100644
--- a/addons/website/views/website_navbar_templates.xml
+++ b/addons/website/views/website_navbar_templates.xml
@@ -3,7 +3,7 @@
<!-- Front-end/Back-end integration -->
<template id="user_navbar" inherit_id="website.layout">
<xpath expr="//t[@t-set='html_data']" position="after">
- <t t-set="body_classname" t-value="'o_connected_user' if env['ir.ui.view'].user_has_groups('base.group_user') else None"/>
+ <t t-set="body_classname" t-value="(body_classname if body_classname else '') + (' o_connected_user' if env['ir.ui.view'].user_has_groups('base.group_user') else '')"/>
</xpath>
<xpath expr="//div[@id='wrapwrap']" position="before">
<nav groups="base.group_user" t-if="website and menu_data" id="oe_main_menu_navbar" class="o_main_navbar">
diff --git a/addons/website_blog/static/src/scss/primary_variables.scss b/addons/website_blog/static/src/scss/primary_variables.scss
index f5cda7678dc..c8b936b59f1 100644
--- a/addons/website_blog/static/src/scss/primary_variables.scss
+++ b/addons/website_blog/static/src/scss/primary_variables.scss
@@ -1,5 +1,3 @@
-$s-latest-posts-big-picture-figure-bg: null;
-
@mixin s-latest-posts-js-get-posts-hook {};
@mixin s-latest-posts-figure-hook {};
diff --git a/addons/website_blog/static/src/scss/website_blog.scss b/addons/website_blog/static/src/scss/website_blog.scss
index 48374f923d1..bfe03363429 100644
--- a/addons/website_blog/static/src/scss/website_blog.scss
+++ b/addons/website_blog/static/src/scss/website_blog.scss
@@ -214,13 +214,10 @@
}
}
-$s-latest-posts-big-picture-figure-bg: $white !default;
-
.s_latest_posts_big_picture {
.s_latest_posts_post {
position: relative;
height: 250px;
- background: $s-latest-posts-big-picture-figure-bg;
text-align: center;
cursor: pointer;
transition: all 300ms;
@@ -233,9 +230,11 @@ $s-latest-posts-big-picture-figure-bg: $white !default;
position: relative;
display: block;
width: 100%;
+ max-width: 100% !important;
height: 100%;
padding: 0;
opacity: 0.8;
+ overflow: hidden;
> .o_blog_cover_component {
@include o-position-absolute(0, 0, 0, 0);
@@ -245,15 +244,17 @@ $s-latest-posts-big-picture-figure-bg: $white !default;
background-position: center;
}
}
- > div, > div > a {
- @include o-position-absolute(0, 0, 0, 0);
- padding: 2em;
- color: $white;
- text-transform: uppercase;
- font-size: 1.25em;
- backface-visibility: hidden;
- &::after, &::before {
- pointer-events: none;
+ > div:not(.o_blog_cover_container):not(.js-loading) {
+ &, & > a {
+ @include o-position-absolute(0, $grid-gutter-width/2, 0, $grid-gutter-width/2);
+ padding: 2em;
+ color: $white;
+ text-transform: uppercase;
+ font-size: 1.25em;
+ backface-visibility: hidden;
+ &::after, &::before {
+ pointer-events: none;
+ }
}
> a {
@@ -280,7 +281,9 @@ $s-latest-posts-big-picture-figure-bg: $white !default;
@include s-latest-posts-figure-hook;
}
- @include s-latest-posts-js-get-posts-hook;
+ .js_get_posts {
+ @include s-latest-posts-js-get-posts-hook;
+ }
}
.s_latest_posts, .s_latest_posts_big_picture {
diff --git a/addons/website_blog/views/website_blog_templates.xml b/addons/website_blog/views/website_blog_templates.xml
index e0496d09cfb..cd5534f29c5 100644
--- a/addons/website_blog/views/website_blog_templates.xml
+++ b/addons/website_blog/views/website_blog_templates.xml
@@ -611,10 +611,10 @@
</template>
<template id="s_latest_posts" name="Latest Posts - List">
- <section class="s_latest_posts">
+ <section class="s_latest_posts pt16 pb16">
<div class="container">
<h2 class="text-center">Latest Posts</h2>
- <div class="row js_get_posts"
+ <div class="row s_col_no_bgcolor js_get_posts"
data-loading="true"
data-template="website_blog.s_latest_posts_list_template"/>
</div>
@@ -622,10 +622,10 @@
</template>
<template id="s_latest_posts_big_picture" name="Latest Posts - Big Images">
- <section class="s_latest_posts_big_picture">
+ <section class="s_latest_posts_big_picture pt16 pb16">
<div class="container-fluid">
<h2 class="text-center">Latest Posts</h2>
- <div class="row js_get_posts"
+ <div class="row s_col_no_bgcolor js_get_posts"
data-loading="true"
data-template="website_blog.s_latest_posts_big_picture_template"/>
</div>
diff --git a/addons/website_event_sale/models/sale_order.py b/addons/website_event_sale/models/sale_order.py
index 097ce0adbaf..cd25e6e1943 100644
--- a/addons/website_event_sale/models/sale_order.py
+++ b/addons/website_event_sale/models/sale_order.py
@@ -41,7 +41,7 @@ class SaleOrder(models.Model):
values['product_id'] = ticket.product_id.id
values['event_id'] = ticket.event_id.id
values['event_ticket_id'] = ticket.id
- values['price_unit'] = ticket.price_reduce or ticket.price
+ values['price_unit'] = ticket.price_reduce
values['name'] = ticket.get_ticket_multiline_description_sale()
# avoid writing related values that end up locking the product record
diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py
index 03b45d6bf17..3c29e8cf9fe 100644
--- a/addons/website_forum/models/forum.py
+++ b/addons/website_forum/models/forum.py
@@ -11,6 +11,7 @@ from odoo.addons.gamification.models.gamification_karma_rank import KarmaError
from odoo import api, fields, models, tools, SUPERUSER_ID, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools import misc
+from odoo.tools.translate import html_translate
_logger = logging.getLogger(__name__)
@@ -28,7 +29,7 @@ class Forum(models.Model):
# description and use
name = fields.Char('Forum Name', required=True, translate=True)
active = fields.Boolean(default=True)
- faq = fields.Html('Guidelines', default=_get_default_faq, translate=True, sanitize=False)
+ faq = fields.Html('Guidelines', default=_get_default_faq, translate=html_translate, sanitize=False)
description = fields.Text(
'Description',
translate=True,
diff --git a/addons/website_forum/static/src/scss/website_forum.scss b/addons/website_forum/static/src/scss/website_forum.scss
index aadd4802fd4..9d0b796cb00 100644
--- a/addons/website_forum/static/src/scss/website_forum.scss
+++ b/addons/website_forum/static/src/scss/website_forum.scss
@@ -220,8 +220,9 @@ img.o_forum_avatar_big {
}
.o_wforum_elearning_navtabs_container {
- height: $owprofile-tabs-height;
- background-color: #66445e;
+ @include media-breakpoint-up(md) {
+ background-color: theme-color('secondary');
+ }
}
.website_forum {
diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml
index 5eee7f6b0ee..042fa0364ed 100644
--- a/addons/website_forum/views/website_forum.xml
+++ b/addons/website_forum/views/website_forum.xml
@@ -52,7 +52,7 @@
<div id="wrap" t-attf-class="container #{website_forum_action}">
<div class="row">
- <div class="col o_wprofile_email_validation_container mb16 mt16">
+ <div class="col o_wprofile_email_validation_container flex-grow-1 mb16 mt16">
<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.'"/>
@@ -66,9 +66,9 @@
<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 float-left mr-3" t-attf-src="/web/image/res.users/#{question.create_uid.id}/image_medium"/>
- <b><i class="fa fa-diamond text-secondary"/> <t t-esc="question.create_uid.sudo().karma"/></b>
- <h6>
+ <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>
diff --git a/addons/website_hr_recruitment/models/hr_recruitment.py b/addons/website_hr_recruitment/models/hr_recruitment.py
index ae6be0baad7..69aa3aaf062 100644
--- a/addons/website_hr_recruitment/models/hr_recruitment.py
+++ b/addons/website_hr_recruitment/models/hr_recruitment.py
@@ -47,7 +47,7 @@ class Job(models.Model):
default_description = self.env["ir.model.data"].xmlid_to_object("website_hr_recruitment.default_website_description")
return (default_description.render() if default_description else "")
- website_description = fields.Html('Website description', translate=html_translate, sanitize_attributes=False, default=_get_default_website_description)
+ website_description = fields.Html('Website description', translate=html_translate, sanitize_attributes=False, default=_get_default_website_description, prefetch=False)
@api.multi
def _compute_website_url(self):
diff --git a/addons/website_livechat/models/__init__.py b/addons/website_livechat/models/__init__.py
index 13835e02e09..18f7d71a23d 100644
--- a/addons/website_livechat/models/__init__.py
+++ b/addons/website_livechat/models/__init__.py
@@ -2,4 +2,5 @@
from . import im_livechat
from . import website
-from . import res_config_settings \ No newline at end of file
+from . import res_config_settings
+from . import ir_http
diff --git a/addons/website_livechat/models/ir_http.py b/addons/website_livechat/models/ir_http.py
new file mode 100644
index 00000000000..3ab3566502a
--- /dev/null
+++ b/addons/website_livechat/models/ir_http.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+from odoo.osv import expression
+
+
+class IrHttp(models.AbstractModel):
+ _inherit = 'ir.http'
+
+ @classmethod
+ def _get_translation_frontend_modules_domain(cls):
+ domain = super(IrHttp, cls)._get_translation_frontend_modules_domain()
+ return expression.OR([domain, [('name', '=', 'im_livechat')]])
diff --git a/addons/website_livechat/views/website_livechat.xml b/addons/website_livechat/views/website_livechat.xml
index 82580fc6549..e63e380ef57 100644
--- a/addons/website_livechat/views/website_livechat.xml
+++ b/addons/website_livechat/views/website_livechat.xml
@@ -167,19 +167,21 @@
</h5>
<div class="col-lg-12">
<div class="row">
- <div class="col-lg-4 pl-0 pr-0">
- <img t-attf-src='/rating/static/src/img/rating_10.png' alt="Great" width="16px" height="16px"/>
- <span class="align-middle"><t t-esc="ratings_per_user[user.partner_id.id]['great']"/>%</span>
- </div>
- <div class="col-lg-4 pl-0 pr-0">
- <img t-attf-src='/rating/static/src/img/rating_5.png' alt="Okay" width="16px" height="16px"/>
- <span class="align-middle"><t t-esc="ratings_per_user[user.partner_id.id]['okay']"/>%</span>
- </div>
- <div class="col-lg-4 pl-0 pr-0">
- <img t-attf-src='/rating/static/src/img/rating_1.png' alt="Bad" width="16px" height="16px"/>
- <span class="align-middle"><t t-esc="ratings_per_user[user.partner_id.id]['bad']"/>%</span>
- </div>
- <t t-if="user.partner_id.id not in ratings_per_user">
+ <t t-if="user.partner_id.id in ratings_per_user">
+ <div class="col-lg-4 pl-0 pr-0">
+ <img t-attf-src='/rating/static/src/img/rating_10.png' alt="Great" width="16px" height="16px"/>
+ <span class="align-middle"><t t-esc="ratings_per_user[user.partner_id.id]['great']"/>%</span>
+ </div>
+ <div class="col-lg-4 pl-0 pr-0">
+ <img t-attf-src='/rating/static/src/img/rating_5.png' alt="Okay" width="16px" height="16px"/>
+ <span class="align-middle"><t t-esc="ratings_per_user[user.partner_id.id]['okay']"/>%</span>
+ </div>
+ <div class="col-lg-4 pl-0 pr-0">
+ <img t-attf-src='/rating/static/src/img/rating_1.png' alt="Bad" width="16px" height="16px"/>
+ <span class="align-middle"><t t-esc="ratings_per_user[user.partner_id.id]['bad']"/>%</span>
+ </div>
+ </t>
+ <t t-else="">
<div class="col-lg-12 pl-0 pr-0 o_livechat_no_rating">Not rated yet</div>
</t>
</div>
diff --git a/addons/website_profile/static/src/scss/website_profile.scss b/addons/website_profile/static/src/scss/website_profile.scss
index ad1771c5d62..8c3b572955f 100644
--- a/addons/website_profile/static/src/scss/website_profile.scss
+++ b/addons/website_profile/static/src/scss/website_profile.scss
@@ -1,7 +1,11 @@
-$owprofile-color-bg: desaturate(map-get($grays, "200"), 80%);
-$owprofile-tabs-height: 36px;
+// Retrive the tab's height by summ its properties
+$owprofile-tabs-height: ($nav-link-padding-y*2) + ($font-size-base * $line-height-base);
-.o_wprofile_wrap {
+// Overal page bg-color: Blend it 'over' the color choosen by the user
+// ($body-bg), rather than force it replacing the variable's value.
+$owprofile-color-bg: mix($body-bg, #efeff4);
+
+.o_wprofile_body {
background-color: $owprofile-color-bg;
}
@@ -10,16 +14,15 @@ $owprofile-tabs-height: 36px;
}
.o_wprofile_pict {
- @include size(30%);
- border-radius: 100%;
+ @include size(100%);
padding-top: 30%;
background-size: cover;
background-position: center;
@include media-breakpoint-up(md) {
- @include size(100%);
- border-radius: 0;
- padding-top: 100%;
+ padding-top: 70%;
+ border: 1px solid darken(#875A7B, 10%);
+ border-bottom-width: 0;
}
}
@@ -34,6 +37,14 @@ $owprofile-tabs-height: 36px;
}
}
+.o_wprofile_sidebar {
+ border: 1px solid $border-color;
+
+ @include media-breakpoint-up(md) {
+ border-top-width: 0;
+ }
+}
+
.o_wprofile_nav_tabs {
@include media-breakpoint-up(md) {
margin-top: $owprofile-tabs-height * -1;
@@ -42,6 +53,7 @@ $owprofile-tabs-height: 36px;
.nav-link {
border-radius: 0;
border-width: 0 1px;
+ line-height: $line-height-base;
@include o-hover-text-color(rgba(white, 0.8), white);
&, &:hover {
diff --git a/addons/website_profile/views/website_profile.xml b/addons/website_profile/views/website_profile.xml
index 1a457a19958..1f0d22e4123 100644
--- a/addons/website_profile/views/website_profile.xml
+++ b/addons/website_profile/views/website_profile.xml
@@ -84,6 +84,7 @@
<!-- Edit Profile Page -->
<template id="user_profile_edit_main" name="Edit Profile">
+ <t t-set="body_classname" t-value="'o_wprofile_body'"/>
<t t-call="website.layout">
<div id="wrap" class="o_wprofile_wrap">
<div class="container pt-4 pb-5">
@@ -169,7 +170,7 @@
</div>
<div class="form-group col-12">
<label class="mb-1 text-primary" for="description"><span class="font-weight-bold">Biography</span></label>
- <textarea name="description" id="description" style="min-height: 120px" required="True"
+ <textarea name="description" id="description" style="min-height: 120px"
class="form-control load_editor"><t t-esc="user.partner_id.website_description"/></textarea>
</div>
<div class="col">
@@ -185,8 +186,9 @@
<!-- Profile Page -->
<template id="user_profile_main" name="Profile Page">
+ <t t-set="body_classname" t-value="'o_wprofile_body'"/>
<t t-call="website.layout">
- <div id="wrap" class="o_wprofile_wrap">
+ <div id="wrap" class="o_wprofile_wrap mt-0">
<t t-call="website_profile.user_profile_header"/>
<t t-call="website_profile.user_profile_content"/>
</div>
@@ -203,7 +205,7 @@
<div class="row">
<!-- ==== Header Left ==== -->
<div class="col-12 col-md-4 col-lg-3">
- <div t-attf-class="d-flex align-items-start #{'justify-content-between' if (request.env.user == user) else 'justify-content-around' }">
+ <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}">
<i class="fa fa-pencil mr-1"/>EDIT
@@ -217,7 +219,7 @@
<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" 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 validation_email_done 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>
@@ -246,10 +248,10 @@
<!-- ========== SIDEBAR ========== -->
<div class="col-12 col-md-4 col-lg-3 mt-3 mt-md-0">
- <div class="bg-white px-3 py-2 py-md-3 mb-3 mb-md-5">
- <div class="d-flex justify-content-between">
+ <div class="o_wprofile_sidebar bg-white px-3 py-2 py-md-3 mb-3 mb-md-5">
+ <div class="o_wprofile_sidebar_top d-flex justify-content-between">
<div t-if="user.rank_id" class="d-flex align-items-center">
- <small class="font-weight-bold mr-2">Rank:</small>
+ <small class="font-weight-bold mr-2">Current rank:</small>
<img t-attf-src="/web/image/gamification.karma.rank/#{user.rank_id.id}/image_small" height="16" alt="" class="mr-1"/>
<a href="/profile/ranks_badges" t-field="user.rank_id"/>
</div>
@@ -320,7 +322,10 @@
<t t-set="user_points" t-value="0"/>
</t>
<path class="o_pc_circle_bg" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
- <path class="o_pc_circle" t-attf-stroke-dasharray="#{user_points}, 100" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" stroke="url(#gradient)" />
+ <path class="o_pc_circle" t-attf-stroke-dasharray="#{user_points}, 100" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" stroke="url(#gradient)" mask="url(#mask)"/>
+ <mask id="mask">
+ <polygon points="0,0 17.2,0 17.2,10 18,10 18,0 36,0 36,36 0,36" fill="white"/>
+ </mask>
<linearGradient id="gradient">
<stop offset="0%" stop-color="var(--o-pc-color-stop-1)"/>
<stop offset="100%" stop-color="var(--o-pc-color-stop-2)"/>
@@ -416,7 +421,9 @@
<table class="table table-sm mb64">
<tr t-foreach="badges" t-as="badge">
<td class="align-middle">
- <img t-attf-src="/website_profile/static/src/img/badge_#{badge.level}.svg" class="my-1" style="height:2.5em" t-att-alt="badge.level"/>
+ <img t-if="not badge.image and badge.level" t-attf-src="/website_profile/static/src/img/badge_#{badge.level}.svg"
+ class="my-1" style="height:2.5em" t-att-alt="badge.name"/>
+ <img t-else="" t-attf-src="/web/image/gamification.badge/#{badge.id}/image" class="my-1" style="height:2.5em" t-att-alt="badge.name"/>
<h6 t-field="badge.name" class="d-inline my-0"/>
</td>
<td class="align-middle d-none d-md-table-cell">
@@ -446,8 +453,9 @@
All Users Page
-->
<template id="users_page_main" name="Users Page">
+ <t t-set="body_classname" t-value="'o_wprofile_body'"/>
<t t-call="website.layout">
- <div id="wrap" class="o_wprofile_wrap pb-5">
+ <div id="wrap" class="o_wprofile_wrap mt-0 pb-5">
<t t-call="website_profile.users_page_header"/>
<t t-call="website_profile.users_page_content"/>
</div>
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index 78d8e3e2fc3..131ec4adaff 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -239,8 +239,9 @@ class WebsiteSale(http.Controller):
Category = request.env['product.public.category']
search_categories = False
+ search_product = Product.search(domain)
if search:
- categories = Product.search(domain).mapped('public_categ_ids')
+ categories = search_product.mapped('public_categ_ids')
search_categories = Category.search([('id', 'parent_of', categories.ids)] + request.website.website_domain())
categs = search_categories.filtered(lambda c: not c.parent_id)
else:
@@ -255,15 +256,14 @@ class WebsiteSale(http.Controller):
parent_category_ids.append(current_category.parent_id.id)
current_category = current_category.parent_id
- product_count = Product.search_count(domain)
+ product_count = len(search_product)
pager = request.website.pager(url=url, total=product_count, page=page, step=ppg, scope=7, url_args=post)
products = Product.search(domain, limit=ppg, offset=pager['offset'], order=self._get_search_order(post))
ProductAttribute = request.env['product.attribute']
if products:
# get all products without limit
- selected_products = Product.search(domain, limit=False)
- attributes = ProductAttribute.search([('attribute_line_ids.value_ids', '!=', False), ('attribute_line_ids.product_tmpl_id', 'in', selected_products.ids)])
+ attributes = ProductAttribute.search([('attribute_line_ids.value_ids', '!=', False), ('attribute_line_ids.product_tmpl_id', 'in', search_product.ids)])
else:
attributes = ProductAttribute.browse(attributes_ids)
@@ -411,7 +411,7 @@ class WebsiteSale(http.Controller):
return request.render("website_sale.cart", values)
- @http.route(['/shop/cart/update'], type='http', auth="public", methods=['POST'], website=True, csrf=False)
+ @http.route(['/shop/cart/update'], type='http', auth="public", methods=['GET', 'POST'], website=True, csrf=False)
def cart_update(self, product_id, add_qty=1, set_qty=0, **kw):
"""This route is called when adding a product to cart (no options)."""
sale_order = request.website.sale_get_order(force_create=True)
@@ -434,6 +434,10 @@ class WebsiteSale(http.Controller):
product_custom_attribute_values=product_custom_attribute_values,
no_variant_attribute_values=no_variant_attribute_values
)
+
+ if kw.get('express'):
+ return request.redirect("/shop/checkout?express=1")
+
return request.redirect("/shop/cart")
@http.route(['/shop/cart/update_json'], type='json', auth="public", methods=['POST'], website=True, csrf=False)
@@ -604,6 +608,7 @@ class WebsiteSale(http.Controller):
new_values['customer'] = True
new_values['team_id'] = request.website.salesteam_id and request.website.salesteam_id.id
new_values['user_id'] = request.website.salesperson_id and request.website.salesperson_id.id
+ new_values['company_id'] = request.website.company_id.id
new_values['website_id'] = request.website.id
lang = request.lang if request.lang in request.website.mapped('language_ids.code') else None
@@ -967,6 +972,9 @@ class WebsiteSale(http.Controller):
if not order or (order.amount_total and not tx):
return request.redirect('/shop')
+ if order and not order.amount_total and not tx:
+ return request.redirect(order.get_portal_url())
+
# clean context and session, then redirect to the confirmation page
request.website.sale_reset()
if tx and tx.state == 'draft':
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index f092cb5de57..dfe942d2dd8 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -124,7 +124,10 @@ class SaleOrder(models.Model):
pu = currency._convert(pu, order.pricelist_id.currency_id, order.company_id, date)
discount = (pu - price) / pu * 100
if discount < 0:
+ # In case the discount is negative, we don't want to show it to the customer,
+ # but we still want to use the price defined on the pricelist
discount = 0
+ pu = price
else:
pu = product.price
if order.pricelist_id and order.partner_id:
diff --git a/addons/website_sale/static/src/scss/website_sale.scss b/addons/website_sale/static/src/scss/website_sale.scss
index 3689314f28b..81e9653ba43 100644
--- a/addons/website_sale/static/src/scss/website_sale.scss
+++ b/addons/website_sale/static/src/scss/website_sale.scss
@@ -358,6 +358,10 @@
&.oe_list {
height: 200px;
+ .oe_subdescription {
+ max-height: 40px;
+ }
+
section {
background: rgba(100, 100, 100, 0.2);
}
diff --git a/addons/website_sale/tests/test_website_sale_product_attribute_value_config.py b/addons/website_sale/tests/test_website_sale_product_attribute_value_config.py
index 6bd1f72ddc7..89fc591b8e5 100644
--- a/addons/website_sale/tests/test_website_sale_product_attribute_value_config.py
+++ b/addons/website_sale/tests/test_website_sale_product_attribute_value_config.py
@@ -34,8 +34,8 @@ class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueSe
pricelist.discount_policy = 'with_discount'
# CASE: B2B setting
- self.env.ref('account.group_show_line_subtotals_tax_excluded').users |= self.env.user
self.env.ref('account.group_show_line_subtotals_tax_included').users -= self.env.user
+ self.env.ref('account.group_show_line_subtotals_tax_excluded').users |= self.env.user
combination_info = self.computer._get_combination_info()
self.assertEqual(combination_info['price'], 2222 * discount_rate * currency_ratio)
@@ -43,8 +43,8 @@ class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueSe
self.assertEqual(combination_info['has_discounted_price'], False)
# CASE: B2C setting
- self.env.ref('account.group_show_line_subtotals_tax_included').users |= self.env.user
self.env.ref('account.group_show_line_subtotals_tax_excluded').users -= self.env.user
+ self.env.ref('account.group_show_line_subtotals_tax_included').users |= self.env.user
combination_info = self.computer._get_combination_info()
self.assertEqual(combination_info['price'], 2222 * discount_rate * currency_ratio * tax_ratio)
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index c122525c56e..b7ce722f42a 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -1347,7 +1347,7 @@
<div class="js_payment mt-3" t-if="not website_sale_order.amount_total" id="payment_method">
<form target="_self" action="/shop/payment/validate" method="post" class="float-right">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
- <a role="button" class="btn btn-primary a-submit">
+ <a role="button" class="btn btn-primary a-submit" href="#">
<span t-if="order.amount_total &gt; 0">Pay Now <span class="fa fa-chevron-right"></span></span>
<span t-if="order.amount_total == 0">Confirm Order <span class="fa fa-chevron-right"></span></span>
</a>
diff --git a/addons/website_sale_comparison/static/src/js/website_sale_tour_comparison.js b/addons/website_sale_comparison/static/src/js/website_sale_tour_comparison.js
new file mode 100644
index 00000000000..8c115eba011
--- /dev/null
+++ b/addons/website_sale_comparison/static/src/js/website_sale_tour_comparison.js
@@ -0,0 +1,246 @@
+odoo.define('website_sale_comparison.tour_comparison', function (require) {
+ 'use strict';
+
+ var tour = require('web_tour.tour');
+ var rpc = require('web.rpc');
+
+ tour.register('product_comparison', {
+ test: true,
+ url: "/shop",
+ }, [
+ // test from shop page
+ {
+ content: "add first product 'Three-Seat Sofa' in a comparison list",
+ trigger: '.oe_product_cart:contains("Three-Seat Sofa") .o_add_compare',
+ },
+ {
+ content: "check compare button contains one product",
+ trigger: '.o_product_circle:contains(1)',
+ run: function () {},
+ },
+ {
+ content: "check popover is closed when only one product",
+ trigger: 'body:not(:has(.comparator-popover))',
+ run: function () {},
+ },
+ {
+ content: "add second product 'Conference Chair' in a comparison list",
+ trigger: '.oe_product_cart:contains("Conference Chair") .o_add_compare',
+ },
+ {
+ content: "check popover is now open and compare button contains two products",
+ extra_trigger: '.comparator-popover',
+ trigger: ' .o_product_circle:contains(2)',
+ run: function () {},
+ },
+ {
+ content: "check products name are correct in the comparelist",
+ extra_trigger: '.o_product_row:contains("Three-Seat Sofa")',
+ trigger: '.o_product_row:contains("Conference Chair")',
+ run: function () {},
+ },
+ // test form product page
+ {
+ content: "go to product page of customizable desk(with variants)",
+ trigger: '.oe_product_cart a:contains("Customizable Desk")',
+ },
+ {
+ content: "check compare button is still there and contains 2 products",
+ extra_trigger: '#product_details',
+ trigger: '.o_product_circle:contains(2)',
+ run: function () {},
+ },
+ {
+ content: "check popover is closed after changing page",
+ trigger: 'body:not(:has(.comparator-popover))',
+ run: function () {},
+ },
+ {
+ content: "add first variant to comparelist",
+ trigger: '.o_add_compare_dyn',
+ },
+ {
+ content: "check the comparelist is now open and contains 3rd product with correct variant",
+ extra_trigger: '.comparator-popover',
+ trigger: '.o_product_row:contains("Customizable Desk (Steel, White)")',
+ run: function () {},
+ },
+ {
+ content: "select 2nd variant(Black Color)",
+ trigger: '.variant_attribute[data-attribute_name="Color"] input[data-value_name="Black"]',
+ run: function (actions) {
+ $('img[class*="product_detail_img"]').attr('data-image-to-change', 1);
+ actions.click();
+ },
+ },
+ {
+ content: "click on compare button to add in comparison list when variant changed",
+ extra_trigger: 'img[class*="product_detail_img"]:not([data-image-to-change])',
+ trigger: '.o_add_compare_dyn',
+ },
+ {
+ content: "comparelist contains 4th product with correct variant",
+ extra_trigger: '.o_product_circle:contains(4)',
+ trigger: '.o_product_row:contains("Customizable Desk (Steel, Black)")',
+ run: function () {},
+ },
+ {
+ content: "check limit is not reached",
+ trigger: ':not(.o_comparelist_limit_warning)',
+ run: function () {},
+ },
+ {
+ content: "select 3nd variant(Custom)",
+ trigger: '.variant_attribute[data-attribute_name="Legs"] input[data-value_name="Custom"]',
+ },
+ {
+ content: "click on compare button to add in comparison list when variant changed",
+ extra_trigger: 'body:not(:has(.carousel-indicators))', // there is 1 image on the custom variant
+ trigger: '.o_add_compare_dyn',
+ },
+ {
+ content: "check limit is reached",
+ trigger: '.o_comparelist_limit_warning',
+ run: function () {},
+ },
+ {
+ content: "click on compare button",
+ trigger: '.o_comparelist_button a',
+ },
+ // test on compare page
+ {
+ content: "check 1st product contains correct variant",
+ trigger: '.o_product_comparison_table:contains("Conference Chair (Steel)")',
+ run: function () {},
+ },
+ {
+ content: "check 2nd product contains correct variant",
+ trigger: '.o_product_comparison_table:contains("Customizable Desk (Steel, White)")',
+ run: function () {},
+ },
+ {
+ content: "check 3rd product is correctly added",
+ trigger: '.o_product_comparison_table:contains("Customizable Desk (Steel, Black)")',
+ run: function () {},
+ },
+ {
+ content: "check 4th product is correctly added",
+ trigger: '.o_product_comparison_table:contains("Three-Seat Sofa")',
+ run: function () {},
+ },
+ {
+ content: "remove one product from compare table",
+ trigger: '#o_comparelist_table',
+ run: function () {
+ // the content of the link is just a float, and the tour manager considers it invisible
+ $('#o_comparelist_table td:contains("Customizable Desk (Steel, Black)") .o_comparelist_remove').click();
+ },
+ },
+ {
+ content: "check customizable table with black variant is removed",
+ trigger: '#o_comparelist_table:not(:contains("Customizable Desk (Steel, Black)"))',
+ run: function () {},
+ },
+ {
+ content: "open compare menu",
+ extra_trigger: 'body:has(.o_product_row:contains("Three-Seat Sofa") .o_remove)',
+ trigger: '.o_product_panel_header',
+ },
+ {
+ content: "remove product",
+ trigger: '.o_product_row:contains("Three-Seat Sofa") .o_remove',
+ },
+ {
+ content: "click on compare button to reload",
+ trigger: '.o_comparelist_button a',
+ },
+ {
+ content: "check product 'Three-Seat Sofa' is removed",
+ trigger: '#o_comparelist_table:not(:contains("Three-Seat Sofa"))',
+ run: function () {},
+ },
+ {
+ content: "add product 'Conference Chair' to cart",
+ trigger: '.product_summary:contains("Conference Chair") .a-submit:contains("Add to Cart")',
+ },
+ {
+ content: "check product correctly added to cart",
+ trigger: '#cart_products:contains("Conference Chair") .js_quantity[value="1"]',
+ run: function () {},
+ },
+ // test advanced configuration and alternative product
+ {
+ content: "create product with newly created attribute and its values and set alternative product",
+ trigger: 'body',
+ run: function () {
+ rpc.query({
+ model: 'product.attribute',
+ method: 'create',
+ args: [{
+ 'name': 'color',
+ 'type': 'color',
+ 'create_variant': 'dynamic',
+ }],
+ }).then(function (attributeId) {
+ rpc.query({
+ model: 'product.template',
+ method: 'create',
+ args: [{
+ 'name': 'Basic Chair',
+ 'is_published': true,
+ 'attribute_line_ids': [[0, 0, {
+ 'attribute_id': attributeId,
+ 'value_ids': [
+ [0, 0, {'name': 'red', 'attribute_id': attributeId, 'sequence': 1}],
+ [0, 0, {'name': 'blue', 'attribute_id': attributeId, 'sequence': 2}],
+ ],
+ }]],
+ }],
+ }).then(function (productId) {
+ rpc.query({
+ model: 'product.template',
+ method: 'create',
+ args: [{
+ 'name': 'Classic Chair',
+ 'is_published': true,
+ 'attribute_line_ids': [[0, 0, {
+ 'attribute_id': attributeId,
+ 'value_ids': [
+ [0, 0, {'name': 'green', 'attribute_id': attributeId, 'sequence': 3}],
+ [0, 0, {'name': 'yellow', 'attribute_id': attributeId, 'sequence': 4}],
+ ],
+ }]],
+ 'alternative_product_ids': [[6, 0, [productId]]],
+ }],
+ }).then(function () {
+ window.location.href = '/shop?search=Classic Chair';
+ });
+ });
+ });
+ },
+ },
+ {
+ content: "click on product",
+ trigger: '.oe_product_cart a:contains("Classic Chair")',
+ },
+ {
+ content: "click on compare button",
+ trigger: '.btn.btn-primary:not(.btn-block):contains("Compare")',
+ },
+ {
+ content: "check product 'Classic Chair' with first variant (green) is on compare page",
+ trigger: '.o_product_comparison_table:contains("Classic Chair (green)")',
+ run: function () {},
+ },
+ {
+ content: "check alternative product 'Basic Chair' with first variant (red) is on compare page",
+ trigger: '.o_product_comparison_table:contains("Basic Chair (red)")',
+ run: function () {},
+ },
+ {
+ content: "check there is the correct attribute",
+ trigger: '.Uncategorized:contains("color"):contains("red")',
+ run: function () {},
+ },
+ ]);
+});
diff --git a/addons/website_sale_comparison/tests/test_website_sale_comparison.py b/addons/website_sale_comparison/tests/test_website_sale_comparison.py
index 91c68c7508a..1a17da59ab2 100644
--- a/addons/website_sale_comparison/tests/test_website_sale_comparison.py
+++ b/addons/website_sale_comparison/tests/test_website_sale_comparison.py
@@ -55,3 +55,10 @@ class TestWebsiteSaleComparison(odoo.tests.TransactionCase):
self.assertFalse(Website0.viewref(test_view_key, raise_if_not_found=False))
# Check that the specific inherited view is correctly removed
self.assertFalse(Website1.viewref(test_view_key, raise_if_not_found=False))
+
+
+@odoo.tests.tagged('post_install', '-at_install')
+class TestUi(odoo.tests.HttpCase):
+
+ def test_01_admin_tour_product_comparison(self):
+ self.phantom_js("/", "odoo.__DEBUG__.services['web_tour.tour'].run('product_comparison')", "odoo.__DEBUG__.services['web_tour.tour'].tours.product_comparison.ready", login='admin')
diff --git a/addons/website_sale_comparison/views/website_sale_comparison_template.xml b/addons/website_sale_comparison/views/website_sale_comparison_template.xml
index 26468eb57fa..0f7a3627e0a 100644
--- a/addons/website_sale_comparison/views/website_sale_comparison_template.xml
+++ b/addons/website_sale_comparison/views/website_sale_comparison_template.xml
@@ -5,6 +5,7 @@
<xpath expr="." position="inside">
<link rel="stylesheet" type="text/scss" href="/website_sale_comparison/static/src/scss/website_sale_comparison.scss"/>
<script type="text/javascript" src="/website_sale_comparison/static/src/js/website_sale_comparison.js"></script>
+ <script type="text/javascript" src="/website_sale_comparison/static/src/js/website_sale_tour_comparison.js"></script>
</xpath>
</template>
diff --git a/addons/website_sale_slides/__manifest__.py b/addons/website_sale_slides/__manifest__.py
index 074d76ce016..951611eba62 100644
--- a/addons/website_sale_slides/__manifest__.py
+++ b/addons/website_sale_slides/__manifest__.py
@@ -11,8 +11,10 @@
'installable': True,
'auto_install': True,
'data': [
- 'data/website_sale_slides_demo.xml',
'views/slide_channel_views.xml',
'views/website_slides_templates.xml',
- ]
+ ],
+ 'demo': [
+ 'data/website_sale_slides_demo.xml',
+ ],
}
diff --git a/addons/website_sale_slides/views/website_slides_templates.xml b/addons/website_sale_slides/views/website_slides_templates.xml
index ad980a29a7a..39c6747ed26 100644
--- a/addons/website_sale_slides/views/website_slides_templates.xml
+++ b/addons/website_sale_slides/views/website_slides_templates.xml
@@ -6,27 +6,26 @@
<xpath expr="//div[hasclass('o_wslides_js_course_join')]" position="inside">
<t t-if="not channel.is_member and channel.enroll == 'payment'">
<t t-if="channel.product_id.website_published">
- <h4>
- <!-- original discounted price, if any -->
- <span t-att-class="'text-danger oe_default_price %s' % ('' if product_info['has_discounted_price'] else 'd-none')"
- style="text-decoration: line-through; white-space: nowrap;"
- t-esc="product_info['list_price']"
- t-options="{'widget': 'monetary', 'display_currency': product_info['currency_id']}"/>
- <!-- real price -->
- <b class="oe_price" style="white-space: nowrap;"
+ <div t-attf-class="text-center d-flex align-items-center text-center pb-1 #{'justify-content-between' if product_info['has_discounted_price'] else 'justify-content-around'}">
+ <!-- real price -->
+ <div class="oe_price font-weight-bold text-nowrap h2 my-2"
t-esc="product_info['price']"
t-options="{'widget': 'monetary', 'display_currency': product_info['currency_id']}"/>
<span itemprop="price" style="display:none;" t-esc="product_info['price']"/>
<span itemprop="priceCurrency" style="display:none;" t-esc="product_info['currency_id'].name"/>
- </h4>
- <a role="button" class="col-10 btn btn-primary" t-att-href="channel.product_id.website_url">
+ <!-- original discounted price, if any -->
+ <del t-att-class="'text-600 text-nowrap oe_default_price %s' % ('' if product_info['has_discounted_price'] else 'd-none')"
+ t-esc="product_info['list_price']"
+ t-options="{'widget': 'monetary', 'display_currency': product_info['currency_id']}"/>
+ </div>
+ <a role="button" class="btn btn-primary btn-block" t-att-href="'/shop/cart/update?product_id=%s&amp;express=1' % channel.product_id.id">
<span class="cta-title text_small_caps">Buy Course</span>
</a>
</t>
<t t-else="">
- <a role="button" class="col-10 btn btn-light" href="#">
- <span class="cta-title text_small_caps">Course Not Buyable</span>
- </a>
+ <div class="alert my-0 bg-200 text-center">
+ Course Not Buyable
+ </div>
</t>
</t>
</xpath>
diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py
index f504ecc03fd..28b53c89dec 100644
--- a/addons/website_slides/models/slide_channel.py
+++ b/addons/website_slides/models/slide_channel.py
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import math
import uuid
from odoo import api, fields, models, tools, _
@@ -237,7 +236,7 @@ class Channel(models.Model):
for record in self:
completed, completion = mapped_data.get(record.id, (False, 0))
record.completed = completed
- record.completion = math.ceil(100.0 * completion / (record.total_slides or 1))
+ record.completion = round(100.0 * completion / (record.total_slides or 1))
@api.depends('upload_group_ids', 'user_id')
def _compute_can_upload(self):
diff --git a/addons/website_slides/static/src/js/slides_course_fullscreen_player.js b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js
index 15e7889412c..6fa054b6647 100644
--- a/addons/website_slides/static/src/js/slides_course_fullscreen_player.js
+++ b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js
@@ -432,6 +432,7 @@ odoo.define('website_slides.fullscreen', function (require) {
// display quiz slide, or quiz attached to a slide
if (slide.type === 'quiz' || slide.isQuiz) {
+ $content.addClass('bg-white');
var QuizWidget = new Quiz(this, slide);
return QuizWidget.appendTo($content);
}
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 cdd8cc531cd..985d8f446c3 100644
--- a/addons/website_slides/static/src/js/slides_course_join.js
+++ b/addons/website_slides/static/src/js/slides_course_join.js
@@ -65,7 +65,7 @@ var CourseJoinWidget = publicWidget.Widget.extend({
});
publicWidget.registry.websiteSlidesCourseJoin = publicWidget.Widget.extend({
- selector: '.o_wslides_wrap',
+ selector: '.o_wslides_js_course_join_link',
/**
* @override
diff --git a/addons/website_slides/static/src/js/slides_course_quiz.js b/addons/website_slides/static/src/js/slides_course_quiz.js
index 1e5ebaaccfc..c166e24a782 100644
--- a/addons/website_slides/static/src/js/slides_course_quiz.js
+++ b/addons/website_slides/static/src/js/slides_course_quiz.js
@@ -219,6 +219,7 @@ odoo.define('website_slides.quiz', function (require) {
* @param OdooEvent ev
*/
_onAnswerClick: function (ev) {
+ ev.preventDefault();
if (! this.slide.readonly && ! this.slide.completed) {
$(ev.currentTarget).find('input[type=radio]').prop('checked', true);
}
diff --git a/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss b/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
index 301887e87be..36a173dc255 100644
--- a/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
+++ b/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
@@ -1,6 +1,4 @@
-$o-wslides-tabs-height: 36px;
-
.o_wslides_fs_main {
@include o-position-absolute(0,0,0,0);
z-index: 2000;
@@ -97,4 +95,23 @@ $o-wslides-tabs-height: 36px;
}
}
}
-}
+
+ .o_wslides_js_lesson_quiz_question {
+ .list-group-item {
+ font-size: 1rem;
+
+ input:checked + i.fa-circle {
+ color: $primary !important;
+ }
+ }
+
+ &.disabled {
+ opacity: 0.5;
+ pointer-events: none;
+ }
+
+ &.completed-disabled{
+ pointer-events: none;
+ }
+ }
+} \ No newline at end of file
diff --git a/addons/website_slides/static/src/scss/website_slides.scss b/addons/website_slides/static/src/scss/website_slides.scss
index c4797b85995..0f64a46ea8e 100644
--- a/addons/website_slides/static/src/scss/website_slides.scss
+++ b/addons/website_slides/static/src/scss/website_slides.scss
@@ -1,6 +1,12 @@
$MAX-Z-INDEX : 2147483647 !default;
-$o-wslides-tabs-height: 36px;
-$o-wslides-color-bg: desaturate(map-get($grays, "200"), 80%);
+
+// Retrive the tab's height by summ its properties
+$o-wslides-tabs-height: ($nav-link-padding-y*2) + ($font-size-base * $line-height-base);
+
+// Overal page bg-color: Blend it 'over' the color choosen by the user
+// ($body-bg), rather than force it replacing the variable's value.
+$o-wslides-color-bg: mix($body-bg, #efeff4);
+
$o-wslides-color-dark1: #47525f;
$o-wslides-color-dark2: #1f262d;
$o-wslides-color-dark3: #101216;
@@ -14,18 +20,19 @@ $o-wslides-fs-side-width: 300px;
}
.o_wslides_course_pict {
- @include size(100%, 30%);
+ @include size(100%);
padding-top: 30%;
background-size: cover;
background-position: center;
@include media-breakpoint-up(md) {
- @include size(100%);
- padding-top: 100%;
+ padding-top: 70%;
+ border: 1px solid darken(#875A7B, 10%);
+ border-bottom-width: 0;
}
}
-.o_wslides_wrap {
+.o_wslides_body {
background-color: $o-wslides-color-bg;
.o_wslides_home_nav {
@@ -42,11 +49,6 @@ $o-wslides-fs-side-width: 300px;
}
}
- .o_wslides_course_card,
- .o_wslides_lesson_card {
- border-width: 0 0 3px 0;
- }
-
.o_wslides_js_slide_like_up,
.o_wslides_js_slide_like_down {
&:not(.disabled) {
@@ -66,7 +68,11 @@ $o-wslides-fs-side-width: 300px;
&.disabled {
opacity: 0.5;
- cursor: not-allowed;
+ pointer-events: none;
+ }
+
+ &.completed-disabled{
+ pointer-events: none;
}
}
@@ -80,10 +86,16 @@ $o-wslides-fs-side-width: 300px;
opacity: 0.5;
}
+ // Solve an overfow issue caused in some
+ // circumstances by flex containers.
+ hr {
+ min-height: 1px;
+ }
+
// Truncate text descriptions to a specific number of lines.
// If '-webkit-line-clamp' is not supported, a less effective
// 'line-height' fallback will be used instead.
- $truncate-limits: 2, 3, 5, 10;
+ $truncate-limits: 2, 3, 10;
@each $limit in $truncate-limits {
.o_wslides_desc_truncate_#{$limit} {
@@ -109,18 +121,33 @@ $o-wslides-fs-side-width: 300px;
border: none;
}
}
+
+ .o_wprofile_progress_circle {
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 200px;
+ }
}
// New course page
// **************************************************
+.o_wslides_course_sidebar {
+ border: 1px solid $border-color;
+
+ @include media-breakpoint-up(md) {
+ border-top-width: 0;
+ }
+}
+
@mixin o-wslides-tabs($tab-active-color: $o-wslides-color-bg) {
- margin-top: $o-wslides-tabs-height * -1;
+ margin-top: ($o-wslides-tabs-height * -1) + 0.05rem;
border-bottom: 0;
.nav-link {
border-radius: 0;
border-width: 0 1px;
+ line-height: $line-height-base;
@include o-hover-text-color(rgba(white, 0.8), white);
&, &:hover {
diff --git a/addons/website_slides/static/src/xml/slide_quiz.xml b/addons/website_slides/static/src/xml/slide_quiz.xml
index 8b7e871151b..ef884ab2007 100644
--- a/addons/website_slides/static/src/xml/slide_quiz.xml
+++ b/addons/website_slides/static/src/xml/slide_quiz.xml
@@ -8,7 +8,7 @@
</div>
<div t-foreach="widget.quiz.questions" t-as="question"
- t-attf-class="o_wslides_js_lesson_quiz_question my-3 #{widget.slide.completed ? 'disabled' : ''}"
+ t-attf-class="o_wslides_js_lesson_quiz_question my-3 #{widget.readonly ? 'disabled' : ''} #{widget.slide.completed ? 'completed-disabled' : ''}"
t-att-data-question-id="question.id" t-att-data-title="question.question">
<div class="h4">
<small class="text-muted"><span t-esc="question_index+1"/>. </small> <span t-esc="question.question"/>
@@ -17,7 +17,7 @@
<t t-foreach="question.answers" t-as="answer">
<a t-att-data-answer-id="answer.id" href="#"
t-att-data-text="answer.text_value"
- t-attf-class="o_wslides_quiz_answer list-group-item d-flex align-items-center #{widget.slide.completed ? 'disabled bg-transparent' : 'list-group-item-action' } #{widget.slide.completed &amp;&amp; answer.is_correct ? 'list-group-item-success' : '' }">
+ t-attf-class="o_wslides_quiz_answer list-group-item d-flex align-items-center #{widget.readonly ? 'disabled bg-transparent' : 'list-group-item-action'} #{widget.slide.completed &amp;&amp; answer.is_correct ? 'list-group-item-success' : '' }">
<label class="my-0 d-flex align-items-center justify-content-center mr-2">
<input type="radio"
@@ -98,8 +98,7 @@
<button type="button" class="btn btn-light border o_wslides_quiz_btn align-self-end">Next <i class="fa fa-chevron-right"/></button>
</t>
<t t-else="">
- <!-- TODO: end course if no slide left -->
- <a type="button" class="btn btn-light border align-self-end">End course</a>
+ <a type="button" href="/slides" class="btn btn-light border align-self-end">End course</a>
</t>
</div>
</div>
diff --git a/addons/website_slides/views/website_slides_templates_course.xml b/addons/website_slides/views/website_slides_templates_course.xml
index 1b835af6335..38d28900d01 100644
--- a/addons/website_slides/views/website_slides_templates_course.xml
+++ b/addons/website_slides/views/website_slides_templates_course.xml
@@ -99,8 +99,9 @@
<t t-call-assets="web.pdf_js_lib" t-css="false"/>
<script type="text/javascript" src="/website_slides/static/lib/pdfslidesviewer/PDFSlidesViewer.js"></script>
</t>
+ <t t-set="body_classname" t-value="'o_wslides_body'"/>
<t t-call="website.layout">
- <div class="wrap o_wslides_wrap">
+ <div id="wrap" t-attf-class="wrap mt-0">
<t t-set="main_object" t-value="channel"/>
<div t-attf-class="o_wslides_course_header o_wslides_gradient position-relative text-white pb-md-0 pt-2 pt-md-5 #{'pb-3' if channel.channel_type == 'training' else 'o_wslides_course_doc_header pb-5'}">
@@ -111,7 +112,7 @@
<!-- ==== Header Left ==== -->
<div class="col-12 col-md-4 col-lg-3">
<div class="d-flex align-items-start justify-content-around h-100">
- <div class="o_wslides_course_pict d-inline-block mb-2 mt-3 my-md-0" t-attf-style="background-image:url(/web/image/slide.channel/#{channel.id}/image)"/>
+ <div class="o_wslides_course_pict d-inline-block mb-2 mt-3 my-md-0" t-attf-style="background-image:url(/web/image/slide.channel/#{channel.id}/image)" t-ignore="True"/>
</div>
</div>
@@ -232,7 +233,7 @@
<template id="course_sidebar" name="Course Sidebar (infos, CTA)">
<!-- Training-only: Channel sidebar (aka general information + CTAs) -->
- <div class="bg-white px-3 py-2 py-md-3 mb-3 mb-md-5">
+ <div class="o_wslides_course_sidebar bg-white px-3 py-2 py-md-3 mb-3 mb-md-5">
<div class="o_wslides_sidebar_top d-flex justify-content-between">
<div class="o_wslides_js_course_join flex-grow-1">
@@ -364,17 +365,17 @@
</li>
</t>
</ul>
- <div t-if="channel.can_upload" class="o_wslides_content_actions">
- <a class="o_wslides_js_slide_upload mr-1 border btn btn-light text-muted"
+ <div t-if="channel.can_upload" class="o_wslides_content_actions btn-group">
+ <a class="o_wslides_js_slide_upload mr-1 border btn btn-light bg-white"
role="button"
aria-label="Upload Presentation"
href="#"
t-att-data-channel-id="channel.id"
t-att-data-can-upload="channel.can_upload"
- t-att-data-can-publish="channel.can_publish"><i class="fa fa-plus mr-1"/>Add Content</a>
- <a class="o_wslides_js_slide_section_add border btn btn-light text-muted" t-attf-channel_id="#{channel.id}"
+ t-att-data-can-publish="channel.can_publish"><i class="fa fa-plus mr-1 align-middle"/><span>Add Content</span></a>
+ <a class="o_wslides_js_slide_section_add border btn btn-light bg-white" t-attf-channel_id="#{channel.id}"
href="#" role="button"
- groups="website.group_website_publisher"><i class="fa fa-folder-o mr-1"/>Add Section</a>
+ groups="website.group_website_publisher"><i class="fa fa-folder-o mr-1 align-middle"/><span>Add Section</span></a>
</div>
</div>
<div t-field="channel.description_html"/>
@@ -514,35 +515,30 @@
</a>
</div>
<!-- Featured lesson -->
- <div t-if="not search and not search_slide_type and slide_promoted" class="container mb-3">
- <div class="card">
- <div class="card-body d-flex flex-column flex-lg-row">
- <t t-set="image_url" t-value="'/web/image/slide.slide/%s/image' % slide_promoted.id"/>
-
- <t t-if="slide_promoted.is_preview or channel.is_member or is_slides_publisher">
- <a t-attf-href="/slides/slide/#{slug(slide_promoted)}" class="w-100 w-lg-50 mr-0 mr-lg-4 mb-3 mb-lg-0 flex-shrink-0 rounded border">
- <div t-attf-style="background-image:url(#{image_url}); background-size: cover; background-position:center; padding-bottom: 35%;" class="h-lg-100"/>
- </a>
- </t>
- <t t-else="">
- <div class="w-100 w-lg-50 mr-0 mr-lg-4 mb-3 mb-lg-0 flex-shrink-0 rounded border">
- <div t-attf-style="background-image:url(#{image_url}); background-size: cover; background-position:center; padding-bottom: 35%;" class="h-lg-100"/>
- </div>
- </t>
+ <div t-if="not search and not search_slide_type and slide_promoted" class="container py-1 mb-2">
+ <div class="card flex-column flex-lg-row">
+ <t t-set="image_url" t-value="'/web/image/slide.slide/%s/image' % slide_promoted.id"/>
+
+ <a t-if="slide_promoted.is_preview or channel.is_member or is_slides_publisher"
+ t-attf-href="/slides/slide/#{slug(slide_promoted)}" class="w-100 w-lg-50 flex-shrink-0 rounded">
+ <div t-attf-style="background-image:url(#{image_url}); background-size: cover; background-position:center; padding-bottom: 35%;" class="h-lg-100"/>
+ </a>
+ <div t-else="" class="w-100 w-lg-50 flex-shrink-0 rounded">
+ <div t-attf-style="background-image:url(#{image_url}); background-size: cover; background-position:center; padding-bottom: 35%;" class="h-lg-100"/>
+ </div>
- <div>
- <a t-if="slide_promoted.is_preview or channel.is_member or is_slides_publisher"
- t-attf-href="/slides/slide/#{slug(slide_promoted)}"
- class="h4 d-block" t-att-title="slide_promoted.name" t-field="slide_promoted.name"/>
- <h4 t-else="" class="text-muted" t-field="slide_promoted.name"/>
+ <div class="card-body">
+ <a t-if="slide_promoted.is_preview or channel.is_member or is_slides_publisher"
+ t-attf-href="/slides/slide/#{slug(slide_promoted)}"
+ class="h4 d-block" t-att-title="slide_promoted.name" t-field="slide_promoted.name"/>
+ <h4 t-else="" class="text-muted" t-field="slide_promoted.name"/>
- <div t-if="slide_promoted.tag_ids" class="border-top mt-2 pt-1">
- <t t-foreach="slide_promoted.tag_ids" t-as="tag">
- <a t-att-href="'/slides/%s/tag/%s' % (slug(slide_promoted.channel_id), slug(tag))" class="badge badge-light" t-esc="tag.name"/>
- </t>
- </div>
- <div class="o_wslides_desc_truncate_10 mt-3" t-field="slide_promoted.description"/>
+ <div t-if="slide_promoted.tag_ids" class="border-top mt-2 pt-1">
+ <t t-foreach="slide_promoted.tag_ids" t-as="tag">
+ <a t-att-href="'/slides/%s/tag/%s' % (slug(slide_promoted.channel_id), slug(tag))" class="badge badge-light" t-esc="tag.name"/>
+ </t>
</div>
+ <div class="o_wslides_desc_truncate_10 mt-3" t-field="slide_promoted.description"/>
</div>
</div>
</div>
@@ -561,9 +557,10 @@
<t t-foreach="category_data" t-as="category">
<div class="mb-2" t-if="(category['slides'] or channel.can_publish) and (search_category and search_category.id == category['id'] or not search_category)">
<div class="d-flex align-items-center justify-content-between border-bottom pb-2 mb-3" t-if="category['name'] != 'Uncategorized'">
- <h5 class="m-0"><t t-esc="category['name']"/></h5>
- <a t-if="category['id']" t-att-href="'/slides/%s/category/%s' % (slug(channel), category['slug_name'])">View all</a>
- <a t-else="" t-att-href="'/slides/%s?uncategorized=1' % (slug(channel))">View all</a>
+ <t t-set="is_empty_editable" t-value="not category['slides'] and channel.can_publish"/>
+ <h5 t-attf-class="m-0 #{'text-muted' if is_empty_editable else ''}"><t t-esc="category['name']"/></h5>
+ <a t-if="category['id'] and not is_empty_editable" t-att-href="'/slides/%s/category/%s' % (slug(channel), category['slug_name'])">View all</a>
+ <a t-elif="not is_empty_editable" t-att-href="'/slides/%s?uncategorized=1' % (slug(channel))">View all</a>
</div>
<div class="row mx-n2">
<t t-foreach="category['slides']" t-as="slide">
@@ -613,9 +610,9 @@
<div t-else="" class="card-img-top border-bottom o_wslides_gradient" t-attf-style="padding-top: 50%;"/>
</t>
- <div class="card-body">
- <a t-if="can_access" class="card-title h5" t-attf-href="/slides/slide/#{slug(slide)}" t-esc="slide.name"/>
- <span t-else="" class="card-title h5 text-muted" t-esc="slide.name"/>
+ <div class="card-body p-3">
+ <a t-if="can_access" class="card-title h5 mb-2o_wslides_desc_truncate_2" t-attf-href="/slides/slide/#{slug(slide)}" t-esc="slide.name"/>
+ <span t-else="" class="card-title h5 mb-2 o_wslides_desc_truncate_2 text-muted" t-esc="slide.name"/>
<div class="card-subtitle mb-2 text-muted" t-if="slide.is_preview or (not slide.is_published and user.has_group('website.group_website_publisher'))">
<t t-if="slide.is_preview">
<span class="badge badge-info">Free preview</span>
@@ -626,14 +623,14 @@
</div>
<div class="card-text pt-2">
<div class="o_wslides_desc_truncate_3 font-weight-light oe_no_empty" t-field="slide.description"/>
- <div t-if="slide.tag_ids" class="border-top mt-2 pt-1">
+ <div t-if="slide.tag_ids" class="mt-2 pt-1 o_wslides_desc_truncate_2">
<t t-foreach="slide.tag_ids" t-as="tag">
- <a t-att-href="'/slides/%s/tag/%s' % (slug(slide.channel_id), slug(tag))" class="badge badge-light" t-esc="tag.name"/>
+ <a t-att-href="'/slides/all?channel_tag_id=%s' % tag.id" class="badge badge-light" t-esc="tag.name"/>
</t>
</div>
</div>
</div>
- <div class="card-footer border-top-0 text-600">
+ <div class="card-footer bg-white text-600">
<div class="d-flex align-items-center small">
<span class="font-weight-bold mr-auto" t-field="slide.completion_time" t-options='{"widget": "float_time"}'/>
<div class="o_wslides_js_slide_like mr-2">
diff --git a/addons/website_slides/views/website_slides_templates_homepage.xml b/addons/website_slides/views/website_slides_templates_homepage.xml
index 7991462f5af..0bcf0c0d189 100644
--- a/addons/website_slides/views/website_slides_templates_homepage.xml
+++ b/addons/website_slides/views/website_slides_templates_homepage.xml
@@ -3,6 +3,7 @@
<!-- Channels home template -->
<template id='courses_home' name="Odoo Courses Homepage">
+ <t t-set="body_classname" t-value="'o_wslides_body'"/>
<t t-call="website.layout">
<div class="wrap o_wslides_wrap">
<section class="s_banner overflow-hidden bg-900" style="background-image: url(&quot;/website_slides/static/src/img/banner_default.svg&quot;); background-size: cover; background-position: 55% 65%">
@@ -87,7 +88,7 @@
<div class="row mb-5 mt-3 mt-lg-0">
<div class="col-12 col-sm-6 col-lg-12">
<t t-call="website_slides.slides_home_user_profile_small"/>
- <hr class="d-sm-none d-lg-block" />
+ <hr class="d-sm-none d-lg-block"/>
</div>
<div class="col-12 col-sm-6 col-lg-12 d-flex align-items-center">
<t t-call="website_slides.slides_home_user_achievements_small"/>
@@ -163,8 +164,9 @@
<!-- Channel all/main template -->
<template id='courses_all' name="Odoo All Courses">
+ <t t-set="body_classname" t-value="'o_wslides_body'"/>
<t t-call="website.layout">
- <div class="wrap o_wslides_wrap">
+ <div class="wrap">
<section class="s_banner bg-900" style="background-image: url(&quot;/website_slides/static/src/img/banner_default_all.svg&quot;); background-size: cover; background-position: 80% 20%">
<div class="container py-5">
<h1 t-if="search_my" class="display-3 mb-0">My Courses</h1>
@@ -173,14 +175,33 @@
</section>
<div class="container mt16 o_wslides_home_nav position-relative">
<!-- Navbar dynamically composed using displayed channel tag groups. -->
- <nav class="navbar navbar-expand-md navbar-light shadow-sm" style="background: white!important">
- <div class="navbar-nav border-right pr-3">
- <a class="nav-link nav-item" t-att-href="'/slides?%s' % keep_query('debug')"><i class="fa fa-chevron-left"/></a>
+ <nav class="navbar navbar-expand-md navbar-light shadow-sm pl-0" style="background: white!important">
+ <div class="navbar-nav border-right">
+ <a class="nav-link nav-item px-3" t-att-href="'/slides?%s' % keep_query('debug')"><i class="fa fa-chevron-left"/></a>
+ </div>
+ <!-- Clear filtering (mobile)-->
+ <div class="form-inline text-nowrap ml-auto d-md-none" t-if="search_slide_type or search_my or search_tags or search_channel_tag_id">
+ <a t-att-href="'/slides/all?%s' % keep_query('debug')" class="btn btn-info mr-2" role="button" title="Clear filters">
+ <i class="fa fa-eraser"/> Clear filters
+ </a>
</div>
- <button class="navbar-toggler" type="button"
+ <form t-else="" method="GET" class="form-inline o_wslides_nav_navbar_right d-md-none">
+ <!-- Search box (mobile)-->
+ <div class="input-group">
+ <input type="search" name="search" class="form-control"
+ placeholder="Search courses" aria-label="Search"
+ t-att-value="search_term"/>
+ <div class="input-group-append">
+ <button class="btn border border-left-0 oe_search_button" type="submit" aria-label="Search" title="Search">
+ <i class="fa fa-search"/>
+ </button>
+ </div>
+ </div>
+ </form>
+ <button class="navbar-toggler px-1" type="button"
data-toggle="collapse" data-target="#navbarTagGroups"
aria-controls="navbarTagGroups" aria-expanded="false" aria-label="Toggle navigation">
- <span class="navbar-toggler-icon"></span>
+ <span class="navbar-toggler-icon small"/>
</button>
<div class="collapse navbar-collapse" id="navbarTagGroups">
<t t-set="search_tag_groups" t-value="search_tags.mapped('group_id')"/>
@@ -202,16 +223,15 @@
</div>
</li>
</t>
- <!-- clear filtering -->
- <li class="nav-item ml-auto" t-if="search_slide_type or search_my or search_tags or search_channel_tag_id">
- <a t-att-href="'/slides/all?%s' % keep_query('debug')" class="nav-link mr-2"
- role="button" title="Clear filters">
- <i class="fa fa-eraser"/> Clear filters
- </a>
- </li>
</ul>
- <!-- search box -->
- <form method="GET" class="form-inline o_wslides_nav_navbar_right">
+ <!-- Clear filtering (desktop)-->
+ <div class="form-inline ml-auto d-none d-md-flex" t-if="search_slide_type or search_my or search_tags or search_channel_tag_id">
+ <a t-att-href="'/slides/all?%s' % keep_query('debug')" class="btn btn-info text-nowrap mr-2" role="button" title="Clear filters">
+ <i class="fa fa-eraser"/> Clear filters
+ </a>
+ </div>
+ <!-- Search box (desktop) -->
+ <form method="GET" class="form-inline o_wslides_nav_navbar_right d-none d-md-flex">
<div class="input-group">
<input type="search" name="search" class="form-control"
placeholder="Search courses" aria-label="Search"
@@ -262,7 +282,7 @@
<template id='course_card' name="Course Card">
<div class="card w-100 o_wslides_course_card mb-4">
- <t t-set="course_image" t-value="'/web/image/slide.channel/%s/image_large' % channel.id"/>
+ <t t-set="course_image" t-value="'/web/image/slide.channel/%s/image' % channel.id"/>
<a t-attf-href="/slides/#{slug(channel)}" t-title="channel.name">
<div t-if="channel.image_large" class="card-img-top" t-attf-style="padding-top: 50%; background-image: url(#{course_image}); background-size: cover; background-position:center"/>
<div t-else="" class="o_wslides_gradient card-img-top position-relative" style="padding-top: 50%; opacity: 0.8">
@@ -270,7 +290,7 @@
</div>
</a>
<div class="card-body p-3">
- <a class="card-title h5 o_wslides_desc_truncate_2" t-attf-href="/slides/#{slug(channel)}" t-esc="channel.name"/>
+ <a class="card-title h5 mb-2 o_wslides_desc_truncate_2" t-attf-href="/slides/#{slug(channel)}" t-esc="channel.name"/>
<div class="card-text mt-1">
<div class="font-weight-light o_wslides_desc_truncate_3" t-field="channel.description"/>
<div t-if="channel.tag_ids" class="mt-2 pt-1 o_wslides_desc_truncate_2">
@@ -280,7 +300,7 @@
</div>
</div>
</div>
- <div class="card-footer border-top-0 text-600 px-3">
+ <div class="card-footer bg-white text-600 px-3">
<div class="d-flex justify-content-between align-items-center">
<small class="font-weight-bold" t-field="channel.total_time" t-options='{"widget": "float_time"}'/>
<div class="d-flex flex-grow-1 justify-content-end">
@@ -335,7 +355,7 @@
<div class="row o_wslides_home_aside_title">
<div class="col">
<a href="/profile/users" class="float-right">View all</a>
- <h5 class="m-0">Heroes of the week</h5>
+ <h5 class="m-0">Leaderboard of the week</h5>
<hr class="mt-2 pt-2"/>
</div>
</div>
@@ -349,7 +369,7 @@
</t>
</div>
</t>
- <t t-else=""><p class="col mt8">No heroes currently :(</p></t>
+ <t t-else=""><p class="col mt8">No leaderboard currently :(</p></t>
</div>
</div>
</template>
diff --git a/addons/website_slides/views/website_slides_templates_lesson.xml b/addons/website_slides/views/website_slides_templates_lesson.xml
index fcb324a4193..5978d63e8b5 100644
--- a/addons/website_slides/views/website_slides_templates_lesson.xml
+++ b/addons/website_slides/views/website_slides_templates_lesson.xml
@@ -3,6 +3,7 @@
<!-- Slide: main template: detailed view -->
<template id="slide_main" name="Slide Detailed View">
+ <t t-set="body_classname" t-value="'o_wslides_body'"/>
<t t-call="website.layout">
<t t-set="main_object" t-value="slide"/>
<div class="wrap o_wslides_wrap">
@@ -147,6 +148,9 @@
</i>
</div>
<div class="o_wslides_lesson_link_name">
+ <t t-call="website_slides.slide_icon">
+ <t t-set="slide" t-value="aside_slide"/>
+ </t>
<span t-esc="aside_slide.name" class="mr-2"/>
</div>
<div class="ml-auto" t-if="aside_slide.question_ids">
@@ -225,7 +229,7 @@
</div>
<div t-if="slide.tag_ids" class="pb-2">
<t t-foreach="slide.tag_ids" t-as="tag">
- <a t-att-href="'/slides/%s/tag/%s' % (slug(slide.channel_id), slug(tag))" class="badge badge-light py-1 px-2" t-esc="tag.name"/>
+ <a t-att-href="'/slides/%s/tag/%s' % (slug(slide.channel_id), slug(tag))" class="badge badge-info py-1 px-2" t-esc="tag.name"/>
</t>
</div>
<div class="o_wslides_lesson_content_type">
@@ -423,7 +427,7 @@
t-att-data-has-next="1 if next_slide else 0"
t-att-data-next-slide-url="'/slides/slide/%s' % (slug(next_slide)) if next_slide else None">
<div t-foreach="slide.question_ids" t-as="question"
- class="o_wslides_js_lesson_quiz_question mt-3" t-att-data-question-id="question.id" t-att-data-title="question.question">
+ t-att-class="'o_wslides_js_lesson_quiz_question mt-3 %s' % ('completed-disabled' if slide_completed else ('disabled' if not slide.channel_id.is_member else ''))" t-att-data-question-id="question.id" t-att-data-title="question.question">
<div class="h4">
<small class="text-muted"><span t-esc="question_index+1"/>. </small> <span t-esc="question.question"/>
</div>
diff --git a/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml b/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml
index 6c24897cb17..5180385f117 100644
--- a/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml
+++ b/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml
@@ -93,6 +93,7 @@
<div class="ml-2">
<a class="d-block pt-1" href="#">
<div class="d-flex ">
+ <t t-call="website_slides.slide_icon"/>
<div class="o_wslides_fs_slide_name" t-esc="slide.name"/>
</div>
</a>
diff --git a/addons/website_slides/views/website_slides_templates_profile.xml b/addons/website_slides/views/website_slides_templates_profile.xml
index 8fbfd413f55..262c95a7717 100644
--- a/addons/website_slides/views/website_slides_templates_profile.xml
+++ b/addons/website_slides/views/website_slides_templates_profile.xml
@@ -33,6 +33,8 @@
<template id="display_course">
<div class="row">
<div class="col-12 col-lg-6" t-foreach="courses" t-as="course">
+ <!-- We cannot use the course.channel_id.completion here since the slide.channel.partners are fetched in sudo mode, recompute it -->
+ <t t-set="channel_completion" t-value="round(100.0 * course.completion / (course.channel_id.total_slides or 1))" />
<div class="card mb-2">
<div class="card-body o_wprofile_slides_course_card_body p-0 d-flex"
t-attf-onclick="location.href='/slides/#{slug(course.channel_id)}';">
@@ -59,9 +61,9 @@
<div class="d-flex align-items-center">
<div class="progress flex-grow-1" style="height:0.5em">
- <div class="progress-bar bg-primary" t-att-style="'width: '+ str(course.completion)+'%'"/>
+ <div class="progress-bar bg-primary" t-att-style="'width: '+ str(channel_completion)+'%'"/>
</div>
- <small class="font-weight-bold pl-2"><span t-field="course.completion"/> %</small>
+ <small class="font-weight-bold pl-2"><span t-esc="channel_completion"/> %</small>
</div>
</div>
</div>
diff --git a/addons/website_slides_forum/views/website_slides_forum_templates.xml b/addons/website_slides_forum/views/website_slides_forum_templates.xml
index b0342ec4077..82f7b8e9182 100644
--- a/addons/website_slides_forum/views/website_slides_forum_templates.xml
+++ b/addons/website_slides_forum/views/website_slides_forum_templates.xml
@@ -3,7 +3,7 @@
<template id="website_slides_forum_header" inherit_id="website_forum.header">
<xpath expr="//div[hasclass('container')]" position="before">
<t t-if="forum.slide_channel_id">
- <div class="row o_wforum_elearning_navtabs_container">
+ <div class="o_wforum_elearning_navtabs_container">
<div class="container">
<ul class="nav nav-tabs o_wprofile_nav_tabs mt-0 flex-nowrap" role="tablist" id="profile_extra_info_tablist">
<li class="nav-item">
diff --git a/addons/website_slides_survey/models/slide_slide.py b/addons/website_slides_survey/models/slide_slide.py
index 3b477364dcd..c7639917b27 100644
--- a/addons/website_slides_survey/models/slide_slide.py
+++ b/addons/website_slides_survey/models/slide_slide.py
@@ -60,13 +60,12 @@ class Slide(models.Model):
for slide in self.filtered(lambda slide: slide.slide_type == 'certification' and slide.survey_id):
if slide.channel_id.is_member:
user_membership_id_sudo = slide.user_membership_id.sudo()
- quizz_passed = user_membership_id_sudo.survey_quizz_passed
- if not quizz_passed and user_membership_id_sudo.user_input_ids:
+ if user_membership_id_sudo.user_input_ids:
last_user_input = next(user_input for user_input in user_membership_id_sudo.user_input_ids.sorted(
lambda user_input: user_input.create_date, reverse=True
))
certification_urls[slide.id] = last_user_input._get_survey_url()
- elif not user_membership_id_sudo.user_input_ids:
+ else:
user_input = slide.survey_id.sudo()._create_answer(
partner=self.env.user.partner_id,
check_attempts=False,
diff --git a/addons/website_slides_survey/static/src/img/certification.svg b/addons/website_slides_survey/static/src/img/certification.svg
new file mode 100644
index 00000000000..54895cf600f
--- /dev/null
+++ b/addons/website_slides_survey/static/src/img/certification.svg
@@ -0,0 +1 @@
+<svg width="12" height="12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M7.158 11.549a2.005 2.005 0 0 1-3.105-1.286 2.005 2.005 0 0 1-1.286-3.105 2.005 2.005 0 0 1 1.286-3.105 2.005 2.005 0 0 1 3.105-1.286 2.005 2.005 0 0 1 3.105 1.286 2.005 2.005 0 0 1 1.286 3.105 2.005 2.005 0 0 1-1.286 3.105 2.005 2.005 0 0 1-3.105 1.286z" id="a"/><filter filterUnits="objectBoundingBox" id="b"><feMorphology radius=".5" in="SourceAlpha" result="shadowSpreadInner1"/><feOffset in="shadowSpreadInner1" result="shadowOffsetInner1"/><feComposite in="shadowOffsetInner1" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowInnerInner1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0749562937 0" in="shadowInnerInner1"/></filter><path id="d" d="M6.04788912 6.85018391L7.55257375 8.20939291 9.91700506 6 11 6.947144 7.55257375 10 5 7.75171917z"/><filter x="-2.5%" y="-3.8%" width="105%" height="115%" filterUnits="objectBoundingBox" id="c"><feOffset dy=".3" in="SourceAlpha" result="shadowOffsetOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.233200393 0" in="shadowOffsetOuter1"/></filter></defs><g transform="translate(-2 -2)" fill="none" fill-rule="evenodd"><g transform="rotate(22 5.57 9.903)"><use fill="#00C9FF" xlink:href="#a"/><use fill="#000" filter="url(#b)" xlink:href="#a"/><path stroke="#FFF" stroke-width=".7" d="M7.158 11.96a2.355 2.355 0 0 1-3.395-1.407 2.355 2.355 0 0 1-1.406-3.395 2.355 2.355 0 0 1 1.406-3.395 2.355 2.355 0 0 1 3.395-1.406 2.355 2.355 0 0 1 3.395 1.406 2.355 2.355 0 0 1 1.406 3.395 2.355 2.355 0 0 1-1.406 3.395 2.355 2.355 0 0 1-3.395 1.406z"/></g><use fill="#000" filter="url(#c)" xlink:href="#d"/><use fill="#FFF" xlink:href="#d"/></g></svg> \ No newline at end of file
diff --git a/addons/website_slides_survey/static/src/js/slides_course_fullscreen_player.js b/addons/website_slides_survey/static/src/js/slides_course_fullscreen_player.js
index 8ef0af53544..07277be6c57 100644
--- a/addons/website_slides_survey/static/src/js/slides_course_fullscreen_player.js
+++ b/addons/website_slides_survey/static/src/js/slides_course_fullscreen_player.js
@@ -9,15 +9,7 @@ Fullscreen.include({
xmlDependencies: (Fullscreen.prototype.xmlDependencies || []).concat(
["/website_slides_survey/static/src/xml/website_slides_fullscreen.xml"]
),
- _preprocessSlideData: function (slidesDataList){
- slidesDataList = this._super.apply(this, arguments);
- slidesDataList.forEach(function (slideData, index){
- if (slideData.type === "certification"){
- slideData.certificationUrl = '/slides_survey/slide/get_certification_url?slide_id=' + slideData.id;
- }
- });
- return slidesDataList;
- },
+
/**
* Extend the _renderSlide method so that slides of type "certification"
* are also taken into account and rendered correctly
diff --git a/addons/website_slides_survey/static/src/scss/website_profile.scss b/addons/website_slides_survey/static/src/scss/website_profile.scss
deleted file mode 100644
index c912aebf31b..00000000000
--- a/addons/website_slides_survey/static/src/scss/website_profile.scss
+++ /dev/null
@@ -1,26 +0,0 @@
-// Certification Card
-.o_wprofile_slides_certif_card_body {
- height: 66px;
- > div > div:first-child {
- position: absolute;
- padding: 0px;
- top: 8px;
- left: 8px;
- width: 66px;
- height: 66px;
- }
- > div > div:nth-child(3) > span {
- position: absolute;
- right: 32px;
- top: 13px;
- }
- > div > div > div:nth-child(2) {
- position: absolute;
- bottom: 0px;
- right: 32px;
- a {
- margin-left: 8px;
- color: grey;
- }
- }
-}
diff --git a/addons/website_slides_survey/static/src/scss/website_slides_survey.scss b/addons/website_slides_survey/static/src/scss/website_slides_survey.scss
new file mode 100644
index 00000000000..3aaa503352c
--- /dev/null
+++ b/addons/website_slides_survey/static/src/scss/website_slides_survey.scss
@@ -0,0 +1,34 @@
+$o_wss_color_1 : #5B7687;
+$o_wss_color_2 : #485761;
+
+// General
+// **************************************************
+.o_wss_certification_icon {
+ @include size(1.4em, auto);
+}
+
+// Course page
+// **************************************************
+#wrap.o_wss_certification_channel {
+ .o_wslides_course_header {
+ background-image: linear-gradient(120deg, $o_wss_color_1, $o_wss_color_2);
+ }
+
+ .o_wslides_course_pict {
+ @include media-breakpoint-up(md) {
+ border-color: $o_wss_color_2;
+ }
+ }
+
+ .o_wslides_nav_tabs .nav-link {
+ @include media-breakpoint-up(md) {
+ &, &:hover{
+ border-color: $o_wss_color_1;
+ }
+
+ &.active {
+ border-color: $gray-100;
+ }
+ }
+ }
+}
diff --git a/addons/website_slides_survey/views/assets.xml b/addons/website_slides_survey/views/assets.xml
index 7a31fa078c8..40eae653970 100644
--- a/addons/website_slides_survey/views/assets.xml
+++ b/addons/website_slides_survey/views/assets.xml
@@ -2,9 +2,11 @@
<odoo>
<data>
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Slides Certification">
+ <xpath expr="//link[last()]" position="after">
+ <link rel="stylesheet" type="text/scss" href="/website_slides_survey/static/src/scss/website_slides_survey.scss"/>
+ </xpath>
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/website_slides_survey/static/src/js/slides_upload.js"/>
- <link rel="stylesheet" type="text/scss" href="/website_slides_survey/static/src/scss/website_profile.scss"/>
<script type="text/javascript" src="/website_slides_survey/static/src/js/slides_course_fullscreen_player.js"/>
</xpath>
</template>
diff --git a/addons/website_slides_survey/views/survey_templates.xml b/addons/website_slides_survey/views/survey_templates.xml
index deea71a9c99..074784d75b7 100644
--- a/addons/website_slides_survey/views/survey_templates.xml
+++ b/addons/website_slides_survey/views/survey_templates.xml
@@ -12,5 +12,11 @@
</div>
</xpath>
</template>
+
+ <template id="o_wss_certification_icon">
+ <t t-set="icon_url" t-value="icon_url if icon_url else '/website_slides_survey/static/src/img/certification.svg'"/>
+ <t t-set="icon_classes" t-value="icon_classes if icon_classes else 'o_wss_certification_icon'"/>
+ <img t-att-class="icon_classes" t-att-src="icon_url" alt="Certification icon"/>
+ </template>
</data>
</odoo>
diff --git a/addons/website_slides_survey/views/website_slides_templates_course.xml b/addons/website_slides_survey/views/website_slides_templates_course.xml
index 98aa90c575f..bac6288713a 100644
--- a/addons/website_slides_survey/views/website_slides_templates_course.xml
+++ b/addons/website_slides_survey/views/website_slides_templates_course.xml
@@ -1,6 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
+ <template id="course_main" inherit_id="website_slides.course_main" name="Certification Course Main">
+ <xpath expr="//div[@id='wrap']" position="attributes">
+ <attribute name="t-attf-class" separator=" " add="#{'o_wss_certification_channel' if channel.nbr_certification > 0 else ''}"/>
+ </xpath>
+
+ <xpath expr="//div[@id='courseMainTabContent']//div[@id='home']/t" position="before">
+ <t t-set="first_slide" t-value="channel.slide_ids[0] if len(channel.slide_ids) > 0 else None"/>
+ <div t-if="channel.nbr_certification > 0 and channel.is_member and channel.completion == 0" class="alert alert-success d-flex align-items-center justify-content-between flex-wrap">
+ <div>Begin your <b>certification</b> today!</div>
+
+ <a t-attf-href="#{'/slides_survey/slide/get_certification_url?slide_id=%s' %(first_slide.id) if first_slide.slide_type == 'certification' and channel.total_slides == 1 else '/slides/slide/%s?fullscreen=1' %(slug(first_slide))}" class="btn btn-success mt-2 mt-sm-0">
+ <span>Start Now</span><i class="fa fa-chevron-right ml-2 align-middle"/>
+ </a>
+ </div>
+ </xpath>
+ </template>
+
<template id="slide_icon_inherit_survey" inherit_id="website_slides.slide_icon">
<xpath expr="//i[last()]" position="after">
<i t-if="slide.slide_type == 'certification'" t-att-class="'fa fa-trophy %s' % icon_class"></i>
diff --git a/addons/website_slides_survey/views/website_slides_templates_homepage.xml b/addons/website_slides_survey/views/website_slides_templates_homepage.xml
index bc4be6ba9b2..eaa1783346b 100644
--- a/addons/website_slides_survey/views/website_slides_templates_homepage.xml
+++ b/addons/website_slides_survey/views/website_slides_templates_homepage.xml
@@ -3,7 +3,18 @@
<data>
<template id="courses_home_inherit_survey" inherit_id="website_slides.courses_home">
<xpath expr="//a[hasclass('o_wslides_home_all_slides')]" position="after">
- <a class="nav-link nav-link" href="/slides/all?slide_type=certification"><i class="fa fa-trophy mr-1"/>Certifications</a>
+ <a class="nav-link nav-link d-flex" href="/slides/all?slide_type=certification">
+ <t t-call="website_slides_survey.o_wss_certification_icon"/>
+ <span class="ml-1">Certifications</span>
+ </a>
+ </xpath>
+ </template>
+
+ <template id="course_card_inherit_survey" inherit_id="website_slides.course_card">
+ <xpath expr="//div/div" position="after">
+ <div t-if="channel.nbr_certification > 0" class="position-absolute py-1 px-2 h5" style="right:0; top:0">
+ <t t-call="website_slides_survey.o_wss_certification_icon"/>
+ </div>
</xpath>
</template>
</data>
diff --git a/addons/website_survey/views/website_survey_templates.xml b/addons/website_survey/views/website_survey_templates.xml
index d5a0bae7103..5c496419a7e 100644
--- a/addons/website_survey/views/website_survey_templates.xml
+++ b/addons/website_survey/views/website_survey_templates.xml
@@ -10,5 +10,9 @@
<t t-raw="0"/>
</t>
</xpath>
+ <xpath expr="t" position="before">
+ <t t-set="hide_header" t-value="survey.certificate" />
+ <t t-set="hide_footer" t-value="survey.certificate" />
+ </xpath>
</template>
</odoo>