diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-06-21 15:16:54 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-06-21 15:16:54 +0200 |
| commit | 78b2063b29a321d152df9badb2d0324578ffe2ec (patch) | |
| tree | 8ecfa4d4acec837795d84f75c39dc10ab98929d8 /addons | |
| parent | e416428669fb18b59893b63f1d9769e871ac9321 (diff) | |
| parent | 8747d72f1efbc3f32c41c20d4d3198410639d2aa (diff) | |
[MERGE] forward port branch saas-12.2 up to 8747d72f1ef
Diffstat (limited to 'addons')
71 files changed, 1019 insertions, 687 deletions
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index e711f4a0dfd..65a2ddf53cc 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -98,7 +98,10 @@ class AccountInvoice(models.Model): currency_clause = [('currency_id', '=', currency_id)] if currency_id == company_currency_id: currency_clause = ['|', ('currency_id', '=', False)] + currency_clause - return self.env['account.journal'].search(domain + currency_clause, limit=1) + return ( + self.env['account.journal'].search(domain + currency_clause, limit=1) + or self.env['account.journal'].search(domain, limit=1) + ) @api.model def _default_currency(self): diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 4e6b6adbb90..8aba8cc0a85 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -482,8 +482,8 @@ class AccountMove(models.Model): def action_duplicate(self): self.ensure_one() action = self.env.ref('account.action_move_journal_line').read()[0] - action['target'] = 'inline' action['context'] = dict(self.env.context) + action['context']['form_view_initial_mode'] = 'edit' action['context']['view_no_maturity'] = False action['views'] = [(self.env.ref('account.view_move_form').id, 'form')] action['res_id'] = self.copy().id diff --git a/addons/account/models/account_reconcile_model.py b/addons/account/models/account_reconcile_model.py index 5ab6865831a..a1db4fc2c5f 100644 --- a/addons/account/models/account_reconcile_model.py +++ b/addons/account/models/account_reconcile_model.py @@ -467,6 +467,8 @@ class AccountReconcileModel(models.Model): ( line_partner.partner_id = 0 AND + TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')) != '' + AND ( regexp_split_to_array(TRIM(REGEXP_REPLACE(move.name, '[^0-9|^\s]', '', 'g')),'\s+') && regexp_split_to_array(TRIM(REGEXP_REPLACE(st_line.name, '[^0-9|^\s]', '', 'g')), '\s+') diff --git a/addons/account/wizard/account_invoice_send_views.xml b/addons/account/wizard/account_invoice_send_views.xml index 648dcdec65d..49c78cb32d9 100644 --- a/addons/account/wizard/account_invoice_send_views.xml +++ b/addons/account/wizard/account_invoice_send_views.xml @@ -12,6 +12,7 @@ <field name="composition_mode" invisible="1"/> <field name="composer_id" invisible="1"/> <field name="invoice_ids" invisible="1"/> + <field name="email_from" invisible="1" /> <div name="option_print"> <field name="is_print" /> <b><label for="is_print"/></b> diff --git a/addons/account_voucher/models/account_voucher.py b/addons/account_voucher/models/account_voucher.py index a3080952fcd..44b31a8a129 100644 --- a/addons/account_voucher/models/account_voucher.py +++ b/addons/account_voucher/models/account_voucher.py @@ -305,9 +305,12 @@ class AccountVoucher(models.Model): } # Create one line per tax and fix debit-credit for the move line if there are tax included if (line.tax_ids): - tax_group = line.tax_ids.compute_all(line.price_unit, line.currency_id, line.quantity, line.product_id, self.partner_id) + tax_group = line.tax_ids.compute_all(self._convert(line.price_unit), self.company_id.currency_id, line.quantity, line.product_id, self.partner_id) if move_line['debit']: move_line['debit'] = tax_group['total_excluded'] if move_line['credit']: move_line['credit'] = tax_group['total_excluded'] + Currency = self.env['res.currency'] + company_cur = Currency.browse(company_currency) + current_cur = Currency.browse(current_currency) for tax_vals in tax_group['taxes']: if tax_vals['amount']: tax = self.env['account.tax'].browse([tax_vals['id']]) @@ -328,8 +331,8 @@ class AccountVoucher(models.Model): ctx = {} if self.account_date: ctx['date'] = self.account_date - temp['currency_id'] = current_currency.id - temp['amount_currency'] = company_currency._convert(tax_vals['amount'], current_currency, line.company_id, self.account_date or fields.Date.today(), round=True) + temp['currency_id'] = current_currency + temp['amount_currency'] = company_cur._convert(tax_vals['amount'], current_cur, line.company_id, self.account_date or fields.Date.today(), round=True) self.env['account.move.line'].create(temp) self.env['account.move.line'].create(move_line) diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py index 7ca5360166c..7e83422d922 100644 --- a/addons/event_sale/models/sale_order.py +++ b/addons/event_sale/models/sale_order.py @@ -83,7 +83,9 @@ class SaleOrderLine(models.Model): company = self.event_id.company_id or self.env.user.company_id currency = company.currency_id or self.env.user.company_id.currency_id self.price_unit = currency._convert( - self.event_ticket_id.price, self.order_id.currency_id, self.order_id.company_id, self.order_id.date_order or fields.Date.today()) + self.event_ticket_id.price, self.order_id.currency_id, + self.order_id.company_id or self.env.user.company_id, + self.order_id.date_order or fields.Date.today()) # we call this to force update the default name self.product_id_change() diff --git a/addons/gamification/data/gamification_karma_rank_data.xml b/addons/gamification/data/gamification_karma_rank_data.xml index 3833dd24b7e..f86b827cceb 100644 --- a/addons/gamification/data/gamification_karma_rank_data.xml +++ b/addons/gamification/data/gamification_karma_rank_data.xml @@ -15,7 +15,7 @@ <field name="description_motivational" type="html"> <div class="media align-items-center"> <div class="media-body">Reach the next rank to show the rest of the world you exist!</div> - <img class="ml-3 img img-fluid" style="max-height: 72px;" src="gamification/static/img/rank_student_badge.svg"/> + <img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_student_badge.svg"/> </div> </field> <field name="karma_min">1</field> @@ -28,7 +28,7 @@ <field name="description_motivational" type="html"> <div class="media align-items-center"> <div class="media-body">Reach the next rank to improve your status.</div> - <img class="ml-3 img img-fluid" style="max-height: 72px;" src="gamification/static/img/rank_bachelor_badge.svg"/> + <img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_bachelor_badge.svg"/> </div> </field> <field name="karma_min">100</field> @@ -41,7 +41,7 @@ <field name="description_motivational" type="html"> <div class="media align-items-center"> <div class="media-body">Reach the next rank and become a Master!</div> - <img class="ml-3 img img-fluid" style="max-height: 72px;" src="gamification/static/img/rank_master_badge.svg"/> + <img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_master_badge.svg"/> </div> </field> <field name="karma_min">500</field> @@ -54,7 +54,7 @@ <field name="description_motivational" type="html"> <div class="media align-items-center"> <div class="media-body">Reach the next rank and become a powerful user!</div> - <img class="ml-3 img img-fluid" style="max-height: 72px;" src="gamification/static/img/rank_doctor_badge.svg"/> + <img class="ml-3 img img-fluid" style="max-height: 72px;" src="/gamification/static/img/rank_doctor_badge.svg"/> </div> </field> <field name="karma_min">2000</field> diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index 37e250779ec..78c325981bb 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -55,10 +55,10 @@ class HrExpense(models.Model): tax_ids = fields.Many2many('account.tax', 'expense_tax', 'expense_id', 'tax_id', string='Taxes', states={'done': [('readonly', True)], 'post': [('readonly', True)]}) untaxed_amount = fields.Float("Subtotal", store=True, compute='_compute_amount', digits=dp.get_precision('Account')) total_amount = fields.Monetary("Total", compute='_compute_amount', store=True, currency_field='currency_id', digits=dp.get_precision('Account')) + company_currency_id = fields.Many2one('res.currency', string="Report Company Currency", related='sheet_id.currency_id', store=True, readonly=False) total_amount_company = fields.Monetary("Total (Company Currency)", compute='_compute_total_amount_company', store=True, currency_field='company_currency_id', digits=dp.get_precision('Account')) company_id = fields.Many2one('res.company', string='Company', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.user.company_id) currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id) - company_currency_id = fields.Many2one('res.currency', string="Report Company Currency", related='sheet_id.currency_id', store=True, readonly=False) analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, oldname='analytic_account') analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags', states={'post': [('readonly', True)], 'done': [('readonly', True)]}) account_id = fields.Many2one('account.account', string='Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, default=_default_account_id, help="An expense account is expected") diff --git a/addons/hr_holidays/data/hr_holidays_data.xml b/addons/hr_holidays/data/hr_holidays_data.xml index 9b8b1b78512..6b7ec7f2a6c 100644 --- a/addons/hr_holidays/data/hr_holidays_data.xml +++ b/addons/hr_holidays/data/hr_holidays_data.xml @@ -16,6 +16,7 @@ <record id="holiday_status_hw" model="hr.leave.type"> <field name="name">Home Workings</field> <field name="color_name">lightgreen</field> + <field name="validity_start" eval="False"/> <field name="leave_notif_subtype_id" eval="ref('mt_leave_home_working')"/> </record> diff --git a/addons/hr_holidays/data/hr_holidays_demo.xml b/addons/hr_holidays/data/hr_holidays_demo.xml index b39754605b5..d75e37f8cff 100644 --- a/addons/hr_holidays/data/hr_holidays_demo.xml +++ b/addons/hr_holidays/data/hr_holidays_demo.xml @@ -8,6 +8,7 @@ <field name="allocation_type">fixed_allocation</field> <field name="color_name">blue</field> <field name="request_unit">hour</field> + <field name="validity_start" eval="False"/> </record> <record id="hr_holiday_status_dv" model="hr.leave.type"> @@ -15,6 +16,7 @@ <field name="allocation_type">fixed</field> <field name="color_name">brown</field> <field name="validation_type">both</field> + <field name="validity_start" eval="False"/> </record> <record id="holiday_status_cla" model="hr.leave.type"> diff --git a/addons/hr_holidays/models/hr.py b/addons/hr_holidays/models/hr.py index 2a8a5506217..be5294d847a 100644 --- a/addons/hr_holidays/models/hr.py +++ b/addons/hr_holidays/models/hr.py @@ -168,7 +168,7 @@ class Employee(models.Model): employee.leave_date_to = leave_data.get(employee.id, {}).get('leave_date_to') employee.current_leave_state = leave_data.get(employee.id, {}).get('current_leave_state') employee.current_leave_id = leave_data.get(employee.id, {}).get('current_leave_id') - employee.is_absent = leave_data and leave_data.get(employee.id, {}).get('current_leave_state') not in ['cancel', 'refuse', 'draft'] + employee.is_absent = leave_data.get(employee.id) and leave_data.get(employee.id, {}).get('current_leave_state') not in ['cancel', 'refuse', 'draft'] @api.onchange('parent_id') def _onchange_parent_id(self): diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index 5d3353c889b..f5616500e57 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -522,16 +522,25 @@ class HolidaysRequest(models.Model): @api.constrains('holiday_status_id', 'date_to', 'date_from') def _check_leave_type_validity(self): for leave in self: + vstart = leave.holiday_status_id.validity_start + vstop = leave.holiday_status_id.validity_stop + dfrom = leave.date_from + dto = leave.date_to if leave.holiday_status_id.validity_start and leave.holiday_status_id.validity_stop: - vstart = leave.holiday_status_id.validity_start - vstop = leave.holiday_status_id.validity_stop - dfrom = leave.date_from - dto = leave.date_to - if dfrom and dto and (dfrom.date() < vstart or dto.date() > vstop): raise UserError( _('You can take %s only between %s and %s') % ( leave.holiday_status_id.display_name, leave.holiday_status_id.validity_start, leave.holiday_status_id.validity_stop)) + elif leave.holiday_status_id.validity_start: + if dfrom and (dfrom.date() < vstart): + raise UserError( + _('You can take %s from %s') % ( + leave.holiday_status_id.display_name, leave.holiday_status_id.validity_start)) + elif leave.holiday_status_id.validity_stop: + if dto and (dto.date() > vstop): + raise UserError( + _('You can take %s until %s') % ( + leave.holiday_status_id.display_name, leave.holiday_status_id.validity_stop)) @api.model def create(self, values): diff --git a/addons/hr_holidays/models/hr_leave_type.py b/addons/hr_holidays/models/hr_leave_type.py index 73cb99d98d7..a215a9e30cb 100644 --- a/addons/hr_holidays/models/hr_leave_type.py +++ b/addons/hr_holidays/models/hr_leave_type.py @@ -183,7 +183,7 @@ class HolidaysType(models.Model): if request.leave_type_request_unit == 'hour' else request.number_of_days) - for allocation in allocations: + for allocation in allocations.sudo(): status_dict = result[allocation.holiday_status_id.id] if allocation.state == 'validate': # note: add only validated allocation even for the virtual diff --git a/addons/hr_holidays/tests/test_access_rights.py b/addons/hr_holidays/tests/test_access_rights.py index a5b2423821c..fff9a636bf5 100644 --- a/addons/hr_holidays/tests/test_access_rights.py +++ b/addons/hr_holidays/tests/test_access_rights.py @@ -19,6 +19,7 @@ class TestLeavesRights(TestHrHolidaysBase): 'name': 'Unlimited', 'validation_type': 'hr', 'allocation_type': 'no', + 'validity_start': False, }) self.rd_dept.manager_id = False self.hr_dept.manager_id = False diff --git a/addons/hr_holidays/tests/test_accrual_allocations.py b/addons/hr_holidays/tests/test_accrual_allocations.py index c877643cc2e..270599fa26e 100644 --- a/addons/hr_holidays/tests/test_accrual_allocations.py +++ b/addons/hr_holidays/tests/test_accrual_allocations.py @@ -20,12 +20,14 @@ class TestAccrualAllocations(TestHrHolidaysBase): self.accrual_type = LeaveType.create({ 'name': 'accrual', 'allocation_type': 'fixed', + 'validity_start': False, }) self.unpaid_type = LeaveType.create({ 'name': 'unpaid', 'allocation_type': 'no', 'unpaid': True, + 'validity_start': False, }) self.set_employee_create_date(self.employee_emp_id, '2010-02-03 00:00:00') diff --git a/addons/hr_holidays/tests/test_change_department.py b/addons/hr_holidays/tests/test_change_department.py index 6240594e43f..52206b386e4 100644 --- a/addons/hr_holidays/tests/test_change_department.py +++ b/addons/hr_holidays/tests/test_change_department.py @@ -18,6 +18,7 @@ class TestChangeDepartment(TestHrHolidaysBase): 'name': 'NotLimitedHR', 'allocation_type': 'no', 'validation_type': 'hr', + 'validity_start': False, }) def create_holiday(name, start, end): diff --git a/addons/hr_holidays/tests/test_holidays_flow.py b/addons/hr_holidays/tests/test_holidays_flow.py index 2a825f46c95..9ef8600f61a 100644 --- a/addons/hr_holidays/tests/test_holidays_flow.py +++ b/addons/hr_holidays/tests/test_holidays_flow.py @@ -29,11 +29,13 @@ class TestHolidaysFlow(TestHrHolidaysBase): 'name': 'NotLimitedHR', 'allocation_type': 'no', 'validation_type': 'hr', + 'validity_start': False, }) self.holidays_status_manager = HolidayStatusManagerGroup.create({ 'name': 'NotLimitedManager', 'allocation_type': 'no', 'validation_type': 'manager', + 'validity_start': False, }) HolidaysEmployeeGroup = Requests.sudo(self.user_employee_id) @@ -95,13 +97,15 @@ class TestHolidaysFlow(TestHrHolidaysBase): HolidayStatusManagerGroup.create({ 'name': 'WithMeetingType', 'allocation_type': 'no', - 'categ_id': self.env['calendar.event.type'].sudo(self.user_hrmanager_id).create({'name': 'NotLimitedMeetingType'}).id + 'categ_id': self.env['calendar.event.type'].sudo(self.user_hrmanager_id).create({'name': 'NotLimitedMeetingType'}).id, + 'validity_start': False, }) self.holidays_status_limited = HolidayStatusManagerGroup.create({ 'name': 'Limited', 'allocation_type': 'fixed', 'validation_type': 'both', + 'validity_start': False, }) HolidaysEmployeeGroup = Requests.sudo(self.user_employee_id) diff --git a/addons/hr_holidays/tests/test_hr_leave_type.py b/addons/hr_holidays/tests/test_hr_leave_type.py index 953c87a3981..e9f32264b7c 100644 --- a/addons/hr_holidays/tests/test_hr_leave_type.py +++ b/addons/hr_holidays/tests/test_hr_leave_type.py @@ -16,6 +16,7 @@ class TestHrLeaveType(TestHrHolidaysBase): 'name': 'Paid Time Off', 'time_type': 'leave', 'allocation_type': 'no', + 'validity_start': False, }) leave_1 = self.env['hr.leave'].create({ diff --git a/addons/hr_holidays/tests/test_leave_requests.py b/addons/hr_holidays/tests/test_leave_requests.py index f15f9cfb9e5..3ac45bc974c 100644 --- a/addons/hr_holidays/tests/test_leave_requests.py +++ b/addons/hr_holidays/tests/test_leave_requests.py @@ -32,11 +32,13 @@ class TestLeaveRequests(TestHrHolidaysBase): 'name': 'NotLimitedHR', 'allocation_type': 'no', 'validation_type': 'hr', + 'validity_start': False, }) self.holidays_type_2 = LeaveType.create({ 'name': 'Limited', 'allocation_type': 'fixed', 'validation_type': 'hr', + 'validity_start': False, }) self.holidays_type_3 = LeaveType.create({ 'name': 'TimeNotLimited', @@ -173,3 +175,18 @@ class TestLeaveRequests(TestHrHolidaysBase): 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() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_employee_is_absent(self): + """ Only the concerned employee should be considered absent """ + self.env['hr.leave'].sudo(self.user_employee_id).create({ + 'name': 'Hol11', + 'employee_id': self.employee_emp_id, + 'holiday_status_id': self.holidays_type_1.id, + 'date_from': (fields.Datetime.now() - relativedelta(days=1)), + 'date_to': fields.Datetime.now() + relativedelta(days=1), + 'number_of_days': 2, + }) + (self.employee_emp | self.employee_hrmanager).mapped('is_absent') # compute in batch + self.assertTrue(self.employee_emp.is_absent, "He should be considered absent") + self.assertFalse(self.employee_hrmanager.is_absent, "He should not be considered absent") diff --git a/addons/hr_holidays/tests/test_out_of_office.py b/addons/hr_holidays/tests/test_out_of_office.py index 4c97f465ce1..0850ff3d76e 100644 --- a/addons/hr_holidays/tests/test_out_of_office.py +++ b/addons/hr_holidays/tests/test_out_of_office.py @@ -17,6 +17,7 @@ class TestOutOfOffice(TestHrHolidaysBase): 'name': 'Legal Leaves', 'time_type': 'leave', 'allocation_type': 'no', + 'validity_start': False, }) def test_leave_ooo(self): @@ -87,6 +88,7 @@ class TestOutOfOfficePerformance(TestHrHolidaysBase): 'name': 'Legal Leaves', 'time_type': 'leave', 'allocation_type': 'no', + 'validity_start': False, }) self.leave_date_end = (datetime.today() + relativedelta(days=3)) self.leave = self.env['hr.leave'].create({ diff --git a/addons/l10n_ca/data/account.account.template.csv b/addons/l10n_ca/data/account.account.template.csv index 552031ee2ee..a940690a711 100644 --- a/addons/l10n_ca/data/account.account.template.csv +++ b/addons/l10n_ca/data/account.account.template.csv @@ -1,6 +1,6 @@ "id","name","code","user_type_id/id","chart_template_id/id","reconcile" "chart1141_en","Stock In Hand","1141","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","False" -"chart1145_en","Stock Delivered But Not Billed","1145","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","False" +"chart1145_en","Stock Delivered But Not Billed","1145","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","True" "chart1151_en","Customers Account","1151","account.data_account_type_receivable","l10n_ca.ca_en_chart_template_en","True" "chart1181_en","GST receivable","1181","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","False" "chart1182_en","PST/QST receivable","1182","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","False" @@ -27,7 +27,7 @@ "chart21424_en","Labour Health and Safety to pay","21424","account.data_account_type_current_liabilities","l10n_ca.ca_en_chart_template_en","False" "chart21425_en","Labour Standards to pay","21425","account.data_account_type_current_liabilities","l10n_ca.ca_en_chart_template_en","False" "chart21426_en","Provincial Income Tax","21426","account.data_account_type_current_liabilities","l10n_ca.ca_en_chart_template_en","False" -"chart2171_en","Stock Received But Not Billed","2171","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","False" +"chart2171_en","Stock Received But Not Billed","2171","account.data_account_type_current_assets","l10n_ca.ca_en_chart_template_en","True" "chart2181_en","Salaries to pay","2181","account.data_account_type_current_liabilities","l10n_ca.ca_en_chart_template_en","False" "chart2183_en","Bonus to pay","2183","account.data_account_type_current_liabilities","l10n_ca.ca_en_chart_template_en","False" "chart2184_en","Retroactive Payment to pay","2184","account.data_account_type_current_liabilities","l10n_ca.ca_en_chart_template_en","False" diff --git a/addons/l10n_do/data/account.account.template.csv b/addons/l10n_do/data/account.account.template.csv index 594e5f02df8..7c63aa26fba 100644 --- a/addons/l10n_do/data/account.account.template.csv +++ b/addons/l10n_do/data/account.account.template.csv @@ -21,7 +21,7 @@ do_niif_11050200,11050200,Inventario de Materia Prima,account.data_account_type_ do_niif_11050300,11050300,Inventario en Tránsito,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1105",l10n_do.account_group_1105 do_niif_11050400,11050400,Inventario de Materiales y Suministros,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1105",l10n_do.account_group_1105 do_niif_11050500,11050500,Inventario de Combustibles,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1105",l10n_do.account_group_1105 -do_niif_11050600,11050600,Bienes Enviados No Facturados,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1105",l10n_do.account_group_1105 +do_niif_11050600,11050600,Bienes Enviados No Facturados,account.data_account_type_current_assets,TRUE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1105",l10n_do.account_group_1105 do_niif_11060100,11060100,Deterioro Acum. de Inventario en Almacen,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1106",l10n_do.account_group_1106 do_niif_11060200,11060200,Deterioro Acum. de Materiales y Suministros,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1106",l10n_do.account_group_1106 do_niif_11060300,11060300,Deterioro Acum. de Combustibles,account.data_account_type_current_assets,FALSE,do_chart_template,"account_tag_1,account_tag_11,account_tag_1106",l10n_do.account_group_1106 @@ -99,7 +99,7 @@ do_niif_21020800,21020800,Aporte a Fondo de Pensiones (AFP) por Pagar,account.da do_niif_21020900,21020900,Seguro de Riesgo Laboral (SRL) por Pagar,account.data_account_type_non_current_liabilities,FALSE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2102",l10n_do.account_group_2102 do_niif_21021000,21021000,INFOTEP por Pagar,account.data_account_type_payable,TRUE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2102",l10n_do.account_group_2102 do_niif_21021100,21021100,Tesorería de la Seguridad Social (TSS) por Pagar,account.data_account_type_payable,TRUE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2102",l10n_do.account_group_2102 -do_niif_21021200,21021200,Bienes Recibidos no Facturados,account.data_account_type_non_current_liabilities,FALSE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2102",l10n_do.account_group_2102 +do_niif_21021200,21021200,Bienes Recibidos no Facturados,account.data_account_type_non_current_liabilities,TRUE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2102",l10n_do.account_group_2102 do_niif_21030101,21030101,ITBIS por Venta Servicios,account.data_account_type_non_current_liabilities,FALSE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2103,account_tag_210301",l10n_do.account_group_210301 do_niif_21030102,21030102,ITBIS por Venta Bienes,account.data_account_type_non_current_liabilities,FALSE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2103,account_tag_210301",l10n_do.account_group_210301 do_niif_21030103,21030103,ITBIS por Venta Servicios en Nombre de Terceros,account.data_account_type_non_current_liabilities,FALSE,do_chart_template,"account_tag_2,account_tag_21,account_tag_2103,account_tag_210301",l10n_do.account_group_210301 diff --git a/addons/l10n_mn/data/account.chart.template.csv b/addons/l10n_mn/data/account.chart.template.csv index 3c63ba77781..ac4990fc4a1 100644 --- a/addons/l10n_mn/data/account.chart.template.csv +++ b/addons/l10n_mn/data/account.chart.template.csv @@ -1,2 +1,2 @@ -id,property_account_receivable_id/id,property_account_payable_id/id,property_account_expense_categ_id/id,property_account_income_categ_id/id,income_currency_exchange_account_id/id,expense_currency_exchange_account_id/id -mn_chart_1,account_template_1201_0201,account_template_3101_0201,account_template_7002_1101,account_template_5101_0201,account_template_5301_0201,account_template_5302_0201 +id,property_account_receivable_id/id,property_account_payable_id/id,property_account_expense_categ_id/id,property_account_income_categ_id/id,income_currency_exchange_account_id/id,expense_currency_exchange_account_id/id,property_stock_account_input_categ_id/id,property_stock_account_output_categ_id/id,property_stock_valuation_account_id/id +mn_chart_1,account_template_1201_0201,account_template_3101_0201,account_template_6101_0101,account_template_5101_0101,account_template_5301_0201,account_template_5302_0201,account_template_1407_0101,account_template_1408_0101,account_template_1401_0101 diff --git a/addons/l10n_mn/data/account_chart_template_data.xml b/addons/l10n_mn/data/account_chart_template_data.xml index bb64e86c95e..0d24db96803 100644 --- a/addons/l10n_mn/data/account_chart_template_data.xml +++ b/addons/l10n_mn/data/account_chart_template_data.xml @@ -10,6 +10,7 @@ <field name="transfer_account_code_prefix">1109</field> <field name="code_digits">8</field> <field name="currency_id" ref="base.MNT"/> + <field name="use_anglo_saxon" eval="True"/> </record> </odoo> diff --git a/addons/l10n_ua/data/account.account.template.csv b/addons/l10n_ua/data/account.account.template.csv index 14471cfa320..608880211ed 100644 --- a/addons/l10n_ua/data/account.account.template.csv +++ b/addons/l10n_ua/data/account.account.template.csv @@ -81,8 +81,8 @@ "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_2811",2811,"Товари відвантажені зі складу","account.data_account_type_current_assets",TRUE,"l10n_ua_psbo_chart_template" +"ua_psbp_2812",2812,"Товари отримані на склад","account.data_account_type_current_liabilities",TRUE,"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" @@ -350,7 +350,7 @@ "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_1121",1121,"Відвантажено зі складу без рахунку-фактури","account.data_account_type_current_assets",TRUE,"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" @@ -363,7 +363,7 @@ "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_1201",1201,"Отримано на склад без рахунку-фактури","account.data_account_type_current_liabilities",TRUE,"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" diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index 4b860440f6b..76f316eb3c3 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -187,9 +187,7 @@ class Channel(models.Model): @api.onchange('public') def _onchange_public(self): - if self.public == 'public': - self.alias_contact = 'everyone' - else: + if self.public != 'public' and self.alias_contact == 'everyone': self.alias_contact = 'followers' @api.onchange('moderator_ids') diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js index a11be5335a4..ced23dacc0c 100644 --- a/addons/mail/static/src/js/chatter.js +++ b/addons/mail/static/src/js/chatter.js @@ -437,7 +437,11 @@ var Chatter = Widget.extend({ */ _updateAttachmentCounter: function () { var count = this.record.data.message_attachment_count || 0; - this.$('.o_chatter_attachment_button_count').html(count); + var $element = this.$('.o_chatter_attachment_button_count'); + if (Number($element.html()) !== count) { + this._areAttachmentsLoaded = false; + $element.html(count); + } }, /** * @private diff --git a/addons/mail/static/src/js/form_view.js b/addons/mail/static/src/js/form_view.js index 0e1623d0dcf..6a2a41091aa 100644 --- a/addons/mail/static/src/js/form_view.js +++ b/addons/mail/static/src/js/form_view.js @@ -16,8 +16,9 @@ FormView.include({ init: function () { this._super.apply(this, arguments); - if ('message_ids' in this.fieldsInfo[this.viewType]) { - this.fieldsInfo[this.viewType].message_attachment_count = {}; + var fieldsInfo = this.fieldsInfo[this.viewType]; + if ('message_ids' in fieldsInfo && !('message_attachment_count' in fieldsInfo)) { + fieldsInfo.message_attachment_count = {}; } }, }); diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js index ea6b7adbd8b..180fddd5e3b 100644 --- a/addons/mail/static/tests/chatter_tests.js +++ b/addons/mail/static/tests/chatter_tests.js @@ -242,6 +242,59 @@ QUnit.test('basic rendering', async function (assert) { form.destroy(); }); +QUnit.test('basic rendering: message_attachment_count can be in view standalone', async function (assert) { + assert.expect(1); + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + services: this.services, + arch: '<form string="Partners">' + + '<sheet>' + + '<group>' + + '<field name="message_attachment_count" string="I\'m here"/>' + + '</group>' + + '</sheet>' + + '</form>', + res_id: 2, + }); + + assert.strictEqual(form.$('.o_form_label').text(), "I'm here", + "The field message_attachment_count must be present according to the view's specs"); + + form.destroy(); +}); + +QUnit.test('basic rendering: message_attachment_count can be in view with chatter', async function (assert) { + assert.expect(1); + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + services: this.services, + arch: '<form string="Partners">' + + '<sheet>' + + '<group>' + + '<field name="message_attachment_count" string="I\'m here"/>' + + '</group>' + + '</sheet>' + + '<div class="oe_chatter">' + + '<field name="message_follower_ids" widget="mail_followers"/>' + + '<field name="message_ids" widget="mail_thread"/>' + + '<field name="activity_ids" widget="mail_activity"/>' + + '</div>' + + '</form>', + res_id: 2, + }); + + assert.strictEqual(form.$('.o_form_label').text(), "I'm here", + "The field message_attachment_count must be present according to the view's specs"); + + form.destroy(); +}); + QUnit.test('Activity Done keep feedback on blur', async function (assert) { assert.expect(3); var done = assert.async(); diff --git a/addons/mrp/data/mrp_data.xml b/addons/mrp/data/mrp_data.xml index 1343cbcf146..c9f6b8e608f 100644 --- a/addons/mrp/data/mrp_data.xml +++ b/addons/mrp/data/mrp_data.xml @@ -9,6 +9,7 @@ <field name="padding">5</field> <field name="number_next">1</field> <field name="number_increment">1</field> + <field name="company_id" eval="False"/> </record> <record id="sequence_mrp_unbuild" model="ir.sequence"> diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 8b30be3da44..e6f44a6182b 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -506,6 +506,9 @@ class MrpProduction(models.Model): factor = production.product_uom_id._compute_quantity(production.product_qty, production.bom_id.product_uom_id) / production.bom_id.product_qty boms, lines = production.bom_id.explode(production.product_id, factor, picking_type=production.bom_id.picking_type_id) for bom_line, line_data in lines: + if bom_line.child_bom_id and bom_line.child_bom_id.type == 'phantom' or\ + bom_line.product_id.type not in ['product', 'consu']: + continue moves.append(production._get_move_raw_values(bom_line, line_data)) return moves @@ -513,10 +516,6 @@ class MrpProduction(models.Model): quantity = line_data['qty'] # alt_op needed for the case when you explode phantom bom and all the lines will be consumed in the operation given by the parent bom line alt_op = line_data['parent_line'] and line_data['parent_line'].operation_id.id or False - if bom_line.child_bom_id and bom_line.child_bom_id.type == 'phantom': - return self.env['stock.move'] - if bom_line.product_id.type not in ['product', 'consu']: - return self.env['stock.move'] source_location = self.location_src_id data = { 'sequence': bom_line.sequence, diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py index ddde7e6a064..c7092e539f9 100644 --- a/addons/mrp/tests/test_order.py +++ b/addons/mrp/tests/test_order.py @@ -989,3 +989,36 @@ class TestMrpOrder(TestMrpCommon): self.assertEqual(product_produce.raw_workorder_line_ids[0].product_uom_id, unit, 'Should be the product uom so "unit"') self.assertEqual(product_produce.raw_workorder_line_ids[1].qty_to_consume, 1, 'Should be 1 unit since the tracking is serial and quantity 2.') self.assertEqual(product_produce.raw_workorder_line_ids[1].product_uom_id, unit, 'should be the product uom so "unit"') + + def test_product_type_service_1(self): + # Create finished product + finished_product = self.env['product.product'].create({ + 'name': 'Geyser', + 'type': 'product', + }) + + # Create service type product + product_raw = self.env['product.product'].create({ + 'name': 'raw Geyser', + 'type': 'service', + }) + + # Create bom for finish product + bom = self.env['mrp.bom'].create({ + 'product_id': finished_product.id, + 'product_tmpl_id': finished_product.product_tmpl_id.id, + 'product_uom_id': self.env.ref('uom.product_uom_unit').id, + 'product_qty': 1.0, + 'type': 'normal', + 'bom_line_ids': [(5, 0), (0, 0, {'product_id': product_raw.id})] + }) + + mo_form = Form(self.env['mrp.production']) + mo_form.product_id = finished_product + mo_form.bom_id = bom + mo_form.product_uom_id = self.env.ref('uom.product_uom_unit') + mo_form.product_qty = 1 + mo = mo_form.save() + + # Check Mo is created or not + self.assertTrue(mo, "Mo is created") diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py index d6ac8296356..6cefbd89261 100644 --- a/addons/mrp/wizard/change_production_qty.py +++ b/addons/mrp/wizard/change_production_qty.py @@ -57,6 +57,9 @@ class ChangeProductionQty(models.TransientModel): boms, lines = production.bom_id.explode(production.product_id, factor, picking_type=production.bom_id.picking_type_id) documents = {} for line, line_data in lines: + if line.child_bom_id and line.child_bom_id.type == 'phantom' or\ + line.product_id.type not in ['product', 'consu']: + continue move, old_qty, new_qty = production._update_raw_move(line, line_data) iterate_key = production._get_document_iterate_key(move) if iterate_key: diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py index 6847cb76461..c15aced1802 100644 --- a/addons/payment/models/payment_acquirer.py +++ b/addons/payment/models/payment_acquirer.py @@ -881,7 +881,7 @@ class PaymentTransaction(models.Model): values.update({ 'partner_name': partner.name, - 'partner_lang': partner.lang or 'en_US', + 'partner_lang': partner.lang or self.env.user.lang, 'partner_email': partner.email, 'partner_zip': partner.zip, 'partner_address': _partner_format_address(partner.street or '', partner.street2 or ''), diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 2a1e1490633..c9402bb70c2 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1566,6 +1566,7 @@ exports.Orderline = Backbone.Model.extend({ price_display : this.get_display_price(), price_with_tax : this.get_price_with_tax(), price_without_tax: this.get_price_without_tax(), + price_with_tax_before_discount: this.get_price_with_tax_before_discount(), tax: this.get_tax(), product_description: this.get_product().description, product_description_sale: this.get_product().description_sale, @@ -1629,9 +1630,9 @@ exports.Orderline = Backbone.Model.extend({ }, get_display_price_one: function(){ var rounding = this.pos.currency.rounding; - var price_unit = this.get_unit_price() * (1.0 - (this.get_discount() / 100.0)); + var price_unit = this.get_unit_price(); if (this.pos.config.iface_tax_included !== 'total') { - return round_pr(price_unit, rounding); + return round_pr(price_unit * (1.0 - (this.get_discount() / 100.0)), rounding); } else { var product = this.get_product(); var taxes_ids = product.taxes_id; @@ -1662,6 +1663,9 @@ exports.Orderline = Backbone.Model.extend({ get_price_with_tax: function(){ return this.get_all_prices().priceWithTax; }, + get_price_with_tax_before_discount: function () { + return this.get_all_prices().priceWithTaxBeforeDiscount; + }, get_tax: function(){ return this.get_all_prices().tax; }, @@ -1907,6 +1911,7 @@ exports.Orderline = Backbone.Model.extend({ }); var all_taxes = this.compute_all(product_taxes, price_unit, this.get_quantity(), this.pos.currency.rounding); + var all_taxes_before_discount = this.compute_all(product_taxes, this.get_unit_price(), this.get_quantity(), this.pos.currency.rounding); _(all_taxes.taxes).each(function(tax) { taxtotal += tax.amount; taxdetail[tax.id] = tax.amount; @@ -1916,6 +1921,7 @@ exports.Orderline = Backbone.Model.extend({ "priceWithTax": all_taxes.total_included, "priceWithoutTax": all_taxes.total_excluded, "priceSumTaxVoid": all_taxes.total_void, + "priceWithTaxBeforeDiscount": all_taxes_before_discount.total_included, "tax": taxtotal, "taxDetails": taxdetail, }; diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 17c47b154ad..49079e59c69 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -791,8 +791,8 @@ <left><t t-esc='line.product_name_wrapped[0]' /></left> <right><value t-att-value-decimals='pos.currency.decimals'><t t-esc='line.price_display' /></value></right> </line> + <t t-call="XmlReceiptWrappedProductNameLines"/> </t> - <t t-call="XmlReceiptWrappedProductNameLines"/> <t t-if='!simple'> <line><left><t t-esc='line.product_name_wrapped[0]' /></left></line> <t t-call="XmlReceiptWrappedProductNameLines"/> @@ -812,7 +812,12 @@ <line indent='1'> <left> <value t-att-value-decimals='pos.dp["Product Price"]'> - <t t-esc="line.price_lst" /> + <t t-if="pos.config.iface_tax_included === 'total'"> + <t t-esc="line.price_with_tax_before_discount" /> + </t> + <t t-else=""> + <t t-esc="line.price_lst" /> + </t> </value> </left> </line> diff --git a/addons/product/report/product_pricelist.py b/addons/product/report/product_pricelist.py index e6ccde44d8e..abdb705c529 100644 --- a/addons/product/report/product_pricelist.py +++ b/addons/product/report/product_pricelist.py @@ -28,7 +28,8 @@ class report_product_pricelist(models.AbstractModel): } def _get_quantity(self, data): - return sorted([data['form'][key] for key in data['form'] if key.startswith('qty') and data['form'][key]]) + form = data and data.get('form') or {} + return sorted([form[key] for key in form if key.startswith('qty') and form[key]]) def _get_categories(self, pricelist, products, quantities): categ_data = [] diff --git a/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py b/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py index 74b73173972..4b89e197c03 100644 --- a/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py +++ b/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py @@ -46,6 +46,7 @@ class TestTimesheetHolidays(TestCommonTimesheet): 'timesheet_generate': True, 'timesheet_project_id': self.internal_project.id, 'timesheet_task_id': self.internal_task_leaves.id, + 'validity_start': False, }) self.hr_leave_type_no_ts = self.env['hr.leave.type'].create({ 'name': 'Leave Type without timesheet generation', @@ -53,6 +54,7 @@ class TestTimesheetHolidays(TestCommonTimesheet): 'timesheet_generate': False, 'timesheet_project_id': False, 'timesheet_task_id': False, + 'validity_start': False, }) # HR Officer allocates some leaves to the employee 1 diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 48b10e8a9c6..1c1bfc1db54 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -201,6 +201,9 @@ class SaleOrder(models.Model): @api.depends('pricelist_id', 'date_order', 'company_id') def _compute_currency_rate(self): for order in self: + if not order.company_id: + order.currency_rate = order.currency_id.with_context(date=order.date_order).rate or 1.0 + continue order.currency_rate = self.env['res.currency']._get_conversion_rate(order.company_id.currency_id, order.currency_id, order.company_id, order.date_order) def _compute_access_url(self): diff --git a/addons/sale_mrp/tests/test_sale_mrp_flow.py b/addons/sale_mrp/tests/test_sale_mrp_flow.py index 459892631b1..6f8710a34e8 100644 --- a/addons/sale_mrp/tests/test_sale_mrp_flow.py +++ b/addons/sale_mrp/tests/test_sale_mrp_flow.py @@ -1459,3 +1459,48 @@ class TestSaleMrpFlow(common.SavepointCase): move_component_kg = order.picking_ids[0].move_lines - move_component_unit self.assertEquals(move_component_unit.product_uom_qty, 0.5) self.assertEquals(move_component_kg.product_uom_qty, 0.583) + + def test_product_type_service_1(self): + route_manufacture = self.warehouse.manufacture_pull_id.route_id.id + route_mto = self.warehouse.mto_pull_id.route_id.id + self.uom_unit = self.env.ref('uom.product_uom_unit') + + # Create finished product + finished_product = self.env['product.product'].create({ + 'name': 'Geyser', + 'type': 'product', + 'route_ids': [(4, route_mto), (4, route_manufacture)], + }) + + # Create service type product + product_raw = self.env['product.product'].create({ + 'name': 'raw Geyser', + 'type': 'service', + }) + + # Create bom for finish product + bom = self.env['mrp.bom'].create({ + 'product_id': finished_product.id, + 'product_tmpl_id': finished_product.product_tmpl_id.id, + 'product_uom_id': self.env.ref('uom.product_uom_unit').id, + 'product_qty': 1.0, + 'type': 'normal', + 'bom_line_ids': [(5, 0), (0, 0, {'product_id': product_raw.id})] + }) + + # Create sale order + sale_form = Form(self.env['sale.order']) + sale_form.partner_id = self.env.ref('base.res_partner_1') + with sale_form.order_line.new() as line: + line.name = finished_product.name + line.product_id = finished_product + line.product_uom_qty = 1.0 + line.product_uom = self.uom_unit + line.price_unit = 10.0 + sale_order = sale_form.save() + + sale_order.action_confirm() + + mo = self.env['mrp.production'].search([('product_id', '=', finished_product.id)]) + + self.assertTrue(mo, 'Manufacturing order created.') diff --git a/addons/survey_crm/i18n/az.po b/addons/survey_crm/i18n/az.po deleted file mode 100644 index 868166d06d8..00000000000 --- a/addons/survey_crm/i18n/az.po +++ /dev/null @@ -1,26 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * survey_crm -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server saas~11.5\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-09-18 09:49+0000\n" -"PO-Revision-Date: 2018-08-24 09:26+0000\n" -"Language-Team: Azerbaijani (https://www.transifex.com/odoo/teams/41243/az/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Language: az\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. module: survey_crm -#: model:ir.model,name:survey_crm.model_survey_mail_compose_message -msgid "Email composition wizard for Survey" -msgstr "" - -#. module: survey_crm -#: model:ir.actions.act_window,name:survey_crm.action_partner_survey_mail_crm -msgid "Partner Survey Mailing" -msgstr "" diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index e5384c9b167..9d95ae485a9 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1048,7 +1048,7 @@ class Binary(http.Controller): filename_field=filename_field, download=download, mimetype=mimetype, default_mimetype='image/png', access_token=access_token) - if status == 301 or (status != 200 and download): + if status in [301, 304] or (status != 200 and download): return request.env['ir.http']._response_by_status(status, headers, content) if not content: content = base64.b64encode(self.placeholder(image=placeholder)) diff --git a/addons/web/static/lib/moment/locale/ar.js b/addons/web/static/lib/moment/locale/ar.js index d263e4c4f8d..dbe4e7dd940 100644 --- a/addons/web/static/lib/moment/locale/ar.js +++ b/addons/web/static/lib/moment/locale/ar.js @@ -58,18 +58,18 @@ var pluralize = function (u) { }; }; var months = [ - 'كانون الثاني يناير', - 'شباط فبراير', - 'آذار مارس', - 'نيسان أبريل', - 'أيار مايو', - 'حزيران يونيو', - 'تموز يوليو', - 'آب أغسطس', - 'أيلول سبتمبر', - 'تشرين الأول أكتوبر', - 'تشرين الثاني نوفمبر', - 'كانون الأول ديسمبر' + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر' ]; var ar = moment.defineLocale('ar', { diff --git a/addons/web/static/src/js/views/control_panel/search/add_new_favorite_menu.js b/addons/web/static/src/js/views/control_panel/search/add_new_favorite_menu.js index 0a2498c69df..421fec3e04b 100644 --- a/addons/web/static/src/js/views/control_panel/search/add_new_favorite_menu.js +++ b/addons/web/static/src/js/views/control_panel/search/add_new_favorite_menu.js @@ -12,6 +12,7 @@ var AddNewFavoriteMenu = Widget.extend({ events: _.extend({}, Widget.prototype.events, { 'click .o_save_favorite': '_onSaveFavoriteClick', 'click .o_add_favorite.o_menu_header': '_onMenuHeaderClick', + 'click input[type="checkbox"]': '_onCheckboxClick', 'keyup .o_save_name input': '_onKeyUp', }), /** @@ -108,6 +109,33 @@ var AddNewFavoriteMenu = Widget.extend({ //-------------------------------------------------------------------------- /** + * Additional behavior when a checkbox is clicked upon + * Namely, some checkbox are mutually exclusive + * this function allows this + * + * @private + * @param {jQueryEvent} ev + */ + _onCheckboxClick: function (ev) { + function mutuallyExclusive (elem) { + if (!elem.id) return false; + return ['use_by_default', 'share_all_users'].some(function (str) { + return elem.id.indexOf(str); + }); + } + var $checkboxes = this.$('input[type="checkbox"]'); + var currentCheckBox = ev.target; + + if (mutuallyExclusive(currentCheckBox)) { + for (var i=0; i < $checkboxes.length; i++) { + var checkbox = $checkboxes[i]; + if (checkbox !== currentCheckBox && mutuallyExclusive(checkbox)) { + checkbox.checked = false; + } + } + } + }, + /** * @private * @param {jQueryEvent} ev */ diff --git a/addons/web/static/src/js/views/pivot/pivot_controller.js b/addons/web/static/src/js/views/pivot/pivot_controller.js index ac48ab769ff..fbc08e93b1e 100644 --- a/addons/web/static/src/js/views/pivot/pivot_controller.js +++ b/addons/web/static/src/js/views/pivot/pivot_controller.js @@ -53,7 +53,6 @@ var PivotController = AbstractController.extend({ * @override */ start: function () { - this.$el.toggleClass('o_enable_linking', this.enableLinking); this.$fieldSelection = this.$('.o_field_selection'); core.bus.on('click', this, function () { this.$fieldSelection.empty(); diff --git a/addons/web/static/src/js/views/pivot/pivot_renderer.js b/addons/web/static/src/js/views/pivot/pivot_renderer.js index 101f3734392..29c3424e141 100644 --- a/addons/web/static/src/js/views/pivot/pivot_renderer.js +++ b/addons/web/static/src/js/views/pivot/pivot_renderer.js @@ -283,7 +283,7 @@ var PivotRenderer = AbstractRenderer.extend({ $cell = $('<td>') .data('id', rows[i].id) .data('col_id', rows[i].col_ids[Math.floor(j / nbrMeasures)]) - .toggleClass('o_empty', !value) + .toggleClass('o_empty', _.isUndefined(value)) .addClass('o_pivot_cell_value text-right'); if (value !== undefined) { $cell.append($('<div>', {class: 'o_value'}).html(formatter(value, measure))); diff --git a/addons/web/static/src/js/widgets/pie_chart.js b/addons/web/static/src/js/widgets/pie_chart.js index ddeca1fdcec..a9b0bc01e45 100644 --- a/addons/web/static/src/js/widgets/pie_chart.js +++ b/addons/web/static/src/js/widgets/pie_chart.js @@ -34,6 +34,11 @@ var PieChart = Widget.extend({ title: node.attrs.title || modifiers.title || modifiers.measure, }); + var pieChartContext = JSON.parse(JSON.stringify(record.context)); + delete pieChartContext.graph_mode; + delete pieChartContext.graph_measure; + delete pieChartContext.graph_groupbys; + this.subViewParams = { modelName: record.model, withControlPanel: false, @@ -41,7 +46,7 @@ var PieChart = Widget.extend({ mode: 'pie', }; this.subViewParams.searchQuery = { - context: record.context, + context: pieChartContext, domain: domain, groupBy: [], }; diff --git a/addons/web/static/src/scss/pivot_view.scss b/addons/web/static/src/scss/pivot_view.scss index 83f8fbc58e7..6bc25ee114e 100644 --- a/addons/web/static/src/scss/pivot_view.scss +++ b/addons/web/static/src/scss/pivot_view.scss @@ -87,4 +87,9 @@ right: 6px; top: 9px; } + + .o_pivot_field_menu .dropdown-item.disabled { + color: $text-muted; + cursor: default; + } } diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 564bbc8582e..c925809fab5 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -532,7 +532,7 @@ </div> </div> -<div t-name="PivotView" class="o_pivot"> +<div t-name="PivotView" t-attf-class="o_pivot #{widget.enableLinking ? 'o_enable_linking' : ''}"> <div class="o_field_selection"/> </div> <t t-name="PivotView.buttons"> diff --git a/addons/web/static/tests/views/control_panel_tests.js b/addons/web/static/tests/views/control_panel_tests.js index 2e0a369ab4a..7d033e5f1e4 100644 --- a/addons/web/static/tests/views/control_panel_tests.js +++ b/addons/web/static/tests/views/control_panel_tests.js @@ -418,6 +418,47 @@ QUnit.module('Views', { controlPanel.destroy(); }); + QUnit.test('Favorites Use by Default and Share are exclusive', async function (assert) { + assert.expect(11); + var controlPanel = await createControlPanel({ + model: 'partner', + arch: "<search></search>", + data: this.data, + searchMenuTypes: ['favorite'], + }); + testUtils.dom.click(controlPanel.$('.o_favorites_menu_button')); + testUtils.dom.click(controlPanel.$('button.o_add_favorite')); + var $checkboxes = controlPanel.$('input[type="checkbox"]'); + + assert.strictEqual($checkboxes.length, 2, + '2 checkboxes are present') + + assert.notOk($checkboxes[0].checked, 'Start: None of the checkboxes are checked (1)'); + assert.notOk($checkboxes[1].checked, 'Start: None of the checkboxes are checked (2)'); + + testUtils.dom.click($checkboxes.eq(0)); + assert.ok($checkboxes[0].checked, 'The first checkbox is checked'); + assert.notOk($checkboxes[1].checked, 'The second checkbox is not checked'); + + testUtils.dom.click($checkboxes.eq(1)); + assert.notOk($checkboxes[0].checked, + 'Clicking on the second checkbox checks it, and unchecks the first (1)'); + assert.ok($checkboxes[1].checked, + 'Clicking on the second checkbox checks it, and unchecks the first (2)'); + + testUtils.dom.click($checkboxes.eq(0)); + assert.ok($checkboxes[0].checked, + 'Clicking on the first checkbox checks it, and unchecks the second (1)'); + assert.notOk($checkboxes[1].checked, + 'Clicking on the first checkbox checks it, and unchecks the second (2)'); + + testUtils.dom.click($checkboxes.eq(0)); + assert.notOk($checkboxes[0].checked, 'End: None of the checkboxes are checked (1)'); + assert.notOk($checkboxes[1].checked, 'End: None of the checkboxes are checked (2)'); + + controlPanel.destroy(); + }); + QUnit.test('load filter', async function (assert) { assert.expect(1); diff --git a/addons/web/static/tests/views/pivot_tests.js b/addons/web/static/tests/views/pivot_tests.js index acd8b11b635..0907e471b39 100644 --- a/addons/web/static/tests/views/pivot_tests.js +++ b/addons/web/static/tests/views/pivot_tests.js @@ -106,7 +106,7 @@ QUnit.module('Views', { }, }); - assert.hasClass(pivot.$el,'o_enable_linking', + assert.hasClass(pivot.$('.o_pivot'),'o_enable_linking', "root node should have classname 'o_enable_linking'"); assert.strictEqual(pivot.$('td.o_pivot_cell_value:contains(32)').length, 1, "should contain a pivot cell with the sum of all records"); @@ -269,7 +269,7 @@ QUnit.module('Views', { } }); - assert.hasClass(pivot.$el,'o_enable_linking', + assert.hasClass(pivot.$('.o_pivot'),'o_enable_linking', "root node should have classname 'o_enable_linking'"); await testUtils.dom.click(pivot.$('.o_pivot_cell_value:contains(12)')); // should trigger a do_action @@ -293,7 +293,7 @@ QUnit.module('Views', { }, }); - assert.doesNotHaveClass(pivot.$el, 'o_enable_linking', + assert.doesNotHaveClass(pivot.$('.o_pivot'), 'o_enable_linking', "root node should not have classname 'o_enable_linking'"); assert.containsOnce(pivot, '.o_pivot_cell_value', "should have one cell"); diff --git a/addons/web/tests/test_image.py b/addons/web/tests/test_image.py index e17073d0fb7..d26528b88e3 100644 --- a/addons/web/tests/test_image.py +++ b/addons/web/tests/test_image.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import io +import base64 from PIL import Image @@ -42,3 +43,23 @@ class TestImage(HttpCase): response = self.url_open('/web/image/fake/0/image_no_size') image = Image.open(io.BytesIO(response.content)) self.assertEqual(image.size, (256, 256)) + + def test_02_content_image_Etag_304(self): + """This test makes sure that the 304 response is properly returned if the ETag is properly set""" + + attachment = self.env['ir.attachment'].create({ + 'datas': b"R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=", + 'name': 'testEtag.gif', + 'public': True, + 'datas_fname': 'testEtag.gif', + 'mimetype': 'image/gif', + }) + response = self.url_open('/web/image/%s' % attachment.id) + self.assertEqual(response.status_code, 200) + self.assertEqual(base64.b64encode(response.content), attachment.datas) + + etag = response.headers.get('ETag') + + response2 = self.url_open('/web/image/%s' % attachment.id, headers={"If-None-Match": etag}) + self.assertEqual(response2.status_code, 304) + self.assertEqual(len(response2.content), 0) diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js index aacdfff9e0c..fa642338399 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js @@ -68,6 +68,8 @@ Wysiwyg.include({ } $(document.body).off('.' + this.id); + this.$iframe.parents().removeClass('o_wysiwyg_no_transform'); + this.$target.insertBefore(this.$iframe); delete window.top[this._onUpdateIframeId]; @@ -100,6 +102,10 @@ Wysiwyg.include({ } self.$iframe.toggleClass('o_fullscreen'); self.$iframe.contents().find('body').toggleClass('o_fullscreen'); + + // Hack to avoid a parent of the fullscreen iframe to have a + // transform (otherwise the position: fixed won't work) + self.$iframe.parents().toggleClass('o_wysiwyg_no_transform'); }, isFullscreen: function () { if (!self.$iframe) { diff --git a/addons/web_editor/static/src/scss/wysiwyg_iframe.scss b/addons/web_editor/static/src/scss/wysiwyg_iframe.scss index d019fdce753..1124d903353 100644 --- a/addons/web_editor/static/src/scss/wysiwyg_iframe.scss +++ b/addons/web_editor/static/src/scss/wysiwyg_iframe.scss @@ -10,6 +10,10 @@ iframe.wysiwyg_iframe.o_fullscreen { border: 0; } +.o_wysiwyg_no_transform { + transform: none !important; +} + body.o_in_iframe { background-color: $o-view-background-color; overflow: hidden; diff --git a/addons/website/static/src/scss/compatibility/bs3_for_12_0.scss b/addons/website/static/src/scss/compatibility/bs3_for_12_0.scss index 4f93429aa58..179584b78b9 100644 --- a/addons/website/static/src/scss/compatibility/bs3_for_12_0.scss +++ b/addons/website/static/src/scss/compatibility/bs3_for_12_0.scss @@ -9,10 +9,12 @@ $-compat-gray-map: ( 'gray': '700', 'gray-light': '600', 'gray-lighter': '200', -); +) !default; @each $old, $new in $-compat-gray-map { - @include bg-variant(".bg-#{$old}", gray($new)); - @include text-emphasis-variant(".text-#{$old}", gray($new)); + @if not map-has-key($grays, $old) { + @include bg-variant(".bg-#{$old}", gray($new)); + @include text-emphasis-variant(".text-#{$old}", gray($new)); + } } // Restore media ? @@ -210,9 +212,13 @@ $-compat-breakpoints: ( .hide { display: none !important; } -.show { - display: block !important; -} +// The 'show' class could be supported if defined here and like that, +// unfortunately, BS4 still defines a 'show' class for other purposes which +// conflict with this (tab-pane, fade effects, ...). Adding more complex rules +// won't solve the problem as they would change css rules priorities. +// .show { +// display: block !important; +// } .hidden { display: none !important; } diff --git a/addons/website/static/src/scss/website.wysiwyg.scss b/addons/website/static/src/scss/website.wysiwyg.scss index a4d8cea8729..d3fb73c5591 100644 --- a/addons/website/static/src/scss/website.wysiwyg.scss +++ b/addons/website/static/src/scss/website.wysiwyg.scss @@ -1,641 +1,649 @@ +//------------------------------------------------------------------------------ +// Modifications applied on normal website DOM while being in the editor. +//------------------------------------------------------------------------------ + body.editor_enable { overflow: hidden; padding-top: 0 !important; - // TOP BAR (EDIT) - #web_editor-top-edit { - height: $o-navbar-height; - background-color: $o-we-color-dark; - z-index: $zindex-modal - 9; - position: fixed; - top: 0px; - right: 0px; - width: auto; - white-space: nowrap; - - background-color: rgba(0, 0, 0, 0); - transition: background-color 400ms $o-we-md-ease 0s; - font-family: $o-we-font-family; - - form.navbar-form { - height: $o-navbar-height; - z-index: 1060; - margin: 0; - padding: 0; - position: absolute; - top: -1px; - right: 0; - transition: right 0.4s $o-we-md-ease 0s; - border-left: 1px solid $o-we-color-divider; - background-color: inherit; + #wrapwrap { + height: calc(100vh - #{$o-navbar-height}); + padding-top: 0px; - .btn-group { - height: 100%; - } + .btn { + -webkit-user-select: none; + } + } +} - .btn { - height: 100%; - margin: 0; - padding: 10px; - line-height: 1.2; - font-size: 13px; - font-family: $o-we-font-family; +//------------------------------------------------------------------------------ +// Editor components style. +//------------------------------------------------------------------------------ + +// TOP BAR (EDIT) +#web_editor-top-edit { + height: $o-navbar-height; + background-color: $o-we-color-dark; + z-index: $zindex-modal - 9; + position: fixed; + top: 0px; + right: 0px; + width: auto; + white-space: nowrap; + + background-color: rgba(0, 0, 0, 0); + transition: background-color 400ms $o-we-md-ease 0s; + font-family: $o-we-font-family; + + form.navbar-form { + height: $o-navbar-height; + z-index: 1060; + margin: 0; + padding: 0; + position: absolute; + top: -1px; + right: 0; + transition: right 0.4s $o-we-md-ease 0s; + border-left: 1px solid $o-we-color-divider; + background-color: inherit; + + .btn-group { + height: 100%; + } - transition: all 0.3s ease 0s; - border: none; - border-radius: 0; + .btn { + height: 100%; + margin: 0; + padding: 10px; + line-height: 1.2; + font-size: 13px; + font-family: $o-we-font-family; - .fa { - margin-right: $grid-gutter-width/4; + transition: all 0.3s ease 0s; + border: none; + border-radius: 0; - &.fa-times { - color: $o-we-color-danger; - } - } + .fa { + margin-right: $grid-gutter-width/4; - &.btn-primary { - @include button-variant($o-brand-odoo, $o-brand-odoo); + &.fa-times { + color: $o-we-color-danger; } + } - &.btn-secondary { - @include button-variant($o-we-color-dark, $o-we-color-dark); + &.btn-primary { + @include button-variant($o-brand-odoo, $o-brand-odoo); + } - &:hover { - background: rgba(black, 0.5); - } - } + &.btn-secondary { + @include button-variant($o-we-color-dark, $o-we-color-dark); - &:focus, - &:active, - &:focus:active { - outline: none; + &:hover { + background: rgba(black, 0.5); } } - .dropdown-menu { - left: auto; - right: 0; + &:focus, + &:active, + &:focus:active { + outline: none; } } - } - #wrapwrap { - height: calc(100vh - #{$o-navbar-height}); - padding-top: 0px; - - .btn { - -webkit-user-select: none; + .dropdown-menu { + left: auto; + right: 0; } } +} - // Translations - .oe_translate_examples li { - margin: 10px; - padding: 4px; - } +// Translations +.oe_translate_examples li { + margin: 10px; + padding: 4px; +} - html[lang]>body.editor_enable [data-oe-translation-state] { - background: rgba($o-we-content-to-translate-color, 0.5) !important; +html[lang] > body.editor_enable [data-oe-translation-state] { + background: rgba($o-we-content-to-translate-color, 0.5) !important; - &[data-oe-translation-state="translated"] { - background: rgba($o-we-translated-content-color, 0.5) !important; - } + &[data-oe-translation-state="translated"] { + background: rgba($o-we-translated-content-color, 0.5) !important; + } - &.o_dirty { - background: rgba($o-we-translated-content-color, 0.25) !important; - } + &.o_dirty { + background: rgba($o-we-translated-content-color, 0.25) !important; } +} - // NOTE EDITOR +// NOTE EDITOR - // SNIPPET PANEL - #oe_snippets { - @include o-w-preserve-btn; - @include o-position-absolute(0, auto, 0, -$o-we-sidebar-width); - width: $o-we-sidebar-width; +// SNIPPET PANEL +#oe_snippets { + @include o-w-preserve-btn; + @include o-position-absolute(0, auto, 0, -$o-we-sidebar-width); + width: $o-we-sidebar-width; - border-right: 1px solid $o-we-color-divider; - transition: left 400ms $o-we-md-ease 0s; - background-image: linear-gradient(45deg, $o-we-color-normal, darken($o-we-color-normal, 10%)); + border-right: 1px solid $o-we-color-divider; + transition: left 400ms $o-we-md-ease 0s; + background-image: linear-gradient(45deg, $o-we-color-normal, darken($o-we-color-normal, 10%)); - #snippets_menu { - color: #999999; - background: $o-we-color-dark; - font-family: $o-we-font-family; - height: $o-navbar-height; - line-height: $o-navbar-height; - } + #snippets_menu { + color: #999999; + background: $o-we-color-dark; + font-family: $o-we-font-family; + height: $o-navbar-height; + line-height: $o-navbar-height; + } - #o_scroll { - .o_panel_header { - color: #999999; - } + #o_scroll { + .o_panel_header { + color: #999999; } } +} - .oe_snippet { - background-color: $o-we-color-normal; +.oe_snippet { + background-color: $o-we-color-normal; - .oe_snippet_thumbnail { - .oe_snippet_thumbnail_img { - border: 1px solid $o-we-color-normal; - } + .oe_snippet_thumbnail { + .oe_snippet_thumbnail_img { + border: 1px solid $o-we-color-normal; + } - img.oe_snippet_thumbnail_img {} + img.oe_snippet_thumbnail_img {} - .oe_snippet_thumbnail_title { - border: 1px solid $o-we-color-dark; - color: $o-we-color-text-light; - } + .oe_snippet_thumbnail_title { + border: 1px solid $o-we-color-dark; + color: $o-we-color-text-light; + } - &:hover .oe_snippet_thumbnail_title { - color: $o-we-color-text-lighter; - } + &:hover .oe_snippet_thumbnail_title { + color: $o-we-color-text-lighter; } } +} - // SNIPPET OPTIONS - .colorpicker { - .o_colorpicker_sections { - .note-color-palette>div { - color: $o-we-color-text-light; - } +// SNIPPET OPTIONS +.colorpicker { + .o_colorpicker_sections { + .note-color-palette>div { + color: $o-we-color-text-light; } + } - .o_colorpicker_section { - &:after { - content: ""; - display: table; - clear: both; - } + .o_colorpicker_section { + &:after { + content: ""; + display: table; + clear: both; + } - >button { + >button { - &:hover, - &.selected { - box-shadow: 0px 0px 2px 2px $o-we-color-light; - } + &:hover, + &.selected { + box-shadow: 0px 0px 2px 2px $o-we-color-light; + } - &[data-event="foreColor"] { - background-color: $o-we-color-normal; + &[data-event="foreColor"] { + background-color: $o-we-color-normal; - &:before { - background-color: rgba(white, 0.3); - } + &:before { + background-color: rgba(white, 0.3); } } } + } - .note-palette-title { - color: $o-we-color-text-light; - } + .note-palette-title { + color: $o-we-color-text-light; + } - .palette-reset { - .note-color-reset { - color: desaturate(rgba($o-we-color-danger, 0.6), 40%); + .palette-reset { + .note-color-reset { + color: desaturate(rgba($o-we-color-danger, 0.6), 40%); - &:hover { - color: $o-we-color-danger; - } + &:hover { + color: $o-we-color-danger; } } } +} - // DROPZONES - #wrapwrap .oe_drop_zone { - &.oe_insert { - height: $o-we-dropzone-size; - margin: (-$o-we-dropzone-size/2) 0; +// DROPZONES +#wrapwrap .oe_drop_zone { + &.oe_insert { + height: $o-we-dropzone-size; + margin: (-$o-we-dropzone-size/2) 0; - &:after { - border-bottom: $o-we-dropzone-border; - } + &:after { + border-bottom: $o-we-dropzone-border; + } - &.oe_vertical { - width: $o-we-dropzone-size; - margin: 0 (-$o-we-dropzone-size/2); + &.oe_vertical { + width: $o-we-dropzone-size; + margin: 0 (-$o-we-dropzone-size/2); - &:after { - border-right: $o-we-dropzone-border; - } + &:after { + border-right: $o-we-dropzone-border; } } } +} - // MANIPULATORS - #oe_manipulators { +// MANIPULATORS +#oe_manipulators { - // SNIPPET MANIPULATORS - .oe_overlay { + // SNIPPET MANIPULATORS + .oe_overlay { - // OVERLAY OPTIONS - >.oe_overlay_options { - font-family: $o-we-font-family; + // OVERLAY OPTIONS + >.oe_overlay_options { + font-family: $o-we-font-family; - >.btn-group { - .btn { - height: $o-we-overlay-option-size; - border: 1px solid $o-we-color-dark; - line-height: #{$o-we-overlay-option-size - 2px}; + >.btn-group { + .btn { + height: $o-we-overlay-option-size; + border: 1px solid $o-we-color-dark; + line-height: #{$o-we-overlay-option-size - 2px}; - color: $o-we-color-text-light; - background-color: $o-we-color-light; + color: $o-we-color-text-light; + background-color: $o-we-color-light; + + &:hover { + background-color: lighten($o-we-color-dark, 10%); + } + + &.oe_snippet_remove { + background-color: $o-we-color-danger; + border-color: darken($o-we-color-danger, 20%); &:hover { - background-color: lighten($o-we-color-dark, 10%); + background-color: darken($o-we-color-danger, 20%); } + } - &.oe_snippet_remove { - background-color: $o-we-color-danger; - border-color: darken($o-we-color-danger, 20%); + >.fa { + color: $o-we-color-text-light; + } + } - &:hover { - background-color: darken($o-we-color-danger, 20%); - } + // CUSTOMIZE MENU BUTTON + >.oe_options { + .btn { + background-color: $o-we-color-dark; + color: $o-we-color-text-light; + + &:hover, + &:active, + &:focus { + background-color: lighten($o-we-color-dark, 5%); + border-color: lighten($o-we-color-dark, 10%); } - >.fa { - color: $o-we-color-text-light; + &:before, + &:after { + background-color: $o-we-color-text-light; } } - // CUSTOMIZE MENU BUTTON - >.oe_options { - .btn { - background-color: $o-we-color-dark; + // Open menu + &.show .btn { + // background-color: $o-we-color-light; + } + + // CUSTOMIZE MENU + .dropdown-menu { + border: 1px solid $o-we-color-dark; + background-color: $o-we-color-light; + + .dropdown-item { color: $o-we-color-text-light; - &:hover, - &:active, - &:focus { - background-color: lighten($o-we-color-dark, 5%); - border-color: lighten($o-we-color-dark, 10%); + &:hover { + background-color: fade-out($o-we-color-dark, 0.5); } - &:before, - &:after { - background-color: $o-we-color-text-light; + &.active { + background-color: fade-out($o-we-color-dark, 0.5); } } - // Open menu - &.show .btn { - // background-color: $o-we-color-light; - } - - // CUSTOMIZE MENU - .dropdown-menu { - border: 1px solid $o-we-color-dark; - background-color: $o-we-color-light; + .dropdown-header { + color: wheat; - .dropdown-item { - color: $o-we-color-text-light; - - &:hover { - background-color: fade-out($o-we-color-dark, 0.5); - } - - &.active { - background-color: fade-out($o-we-color-dark, 0.5); - } - } - - .dropdown-header { + &.o_parent_editor_header { color: wheat; - - &.o_parent_editor_header { - color: wheat; - } } + } - .dropdown-submenu { - &::before { - border-left-color: $o-we-color-text-light; - } + .dropdown-submenu { + &::before { + border-left-color: $o-we-color-text-light; } } } } } + } - &.o_top_cover>.oe_overlay_options { - top: $o-we-handle-border-width; - right: $o-we-handle-border-width; - } - + &.o_top_cover>.oe_overlay_options { + top: $o-we-handle-border-width; + right: $o-we-handle-border-width; } + } +} - // Animations - @keyframes fadeInDownSmall { - 0% { - opacity: 0; - transform: translate(0, -5px); - } +// Animations +@keyframes fadeInDownSmall { + 0% { + opacity: 0; + transform: translate(0, -5px); + } - 100% { - opacity: 1; - transform: translate(0, 0); - } + 100% { + opacity: 1; + transform: translate(0, 0); } +} - @keyframes fadeInOut { - 0% { - opacity: 0; - } +@keyframes fadeInOut { + 0% { + opacity: 0; + } - 25% { - opacity: 1; - } + 25% { + opacity: 1; + } - 75% { - opacity: 1; - } + 75% { + opacity: 1; + } - 100% { - opacity: 0; - } + 100% { + opacity: 0; } +} - @keyframes inputHighlighter { - from { - background: $o-brand-primary; - } +@keyframes inputHighlighter { + from { + background: $o-brand-primary; + } - to { - width: 0; - background: transparent; - } + to { + width: 0; + background: transparent; } +} - // MODALS - body .modal { +// MODALS +body .modal { - // BACKGROUND IMAGE OPTIONS - .o_bg_img_opt_modal .o_bg_img_opt { - margin: 15px 0; + // BACKGROUND IMAGE OPTIONS + .o_bg_img_opt_modal .o_bg_img_opt { + margin: 15px 0; - .help-control { - @include o-position-absolute(15px, 15px); - } + .help-control { + @include o-position-absolute(15px, 15px); + } - .help { - margin: 15px 0; + .help { + margin: 15px 0; - .simulator { - position: relative; - float: left; - margin-right: 15px; - width: 120px; - height: 100px; + .simulator { + position: relative; + float: left; + margin-right: 15px; + width: 120px; + height: 100px; + + .bg { + @include o-position-absolute(0, 0, 0, 0); + border: 1px dotted #888787; + background-image: url(/web/image); + background-position: center center; + background-size: cover; + } + .el { + @include o-position-absolute(10px, 0, 10px, 0); + border: 1px solid #222; + } + + &.contain_bg { .bg { - @include o-position-absolute(0, 0, 0, 0); - border: 1px dotted #888787; - background-image: url(/web/image); - background-position: center center; - background-size: cover; + @include o-position-absolute(0, 12px, 20px, 12px); } .el { - @include o-position-absolute(10px, 0, 10px, 0); - border: 1px solid #222; + @include o-position-absolute(0, 0, 20px, 0); } + } + } - &.contain_bg { - .bg { - @include o-position-absolute(0, 12px, 20px, 12px); - } + +* { + clear: left; + } + } - .el { - @include o-position-absolute(0, 0, 20px, 0); - } - } - } + .o_bg_img_opt_cover_edition { + @include o-we-preview-box; + padding: 0 25px 25px 25px; - +* { - clear: left; - } + h6 { + color: white; + font-weight: bold; } - .o_bg_img_opt_cover_edition { - @include o-we-preview-box; - padding: 0 25px 25px 25px; + .o_bg_img_opt_object { + position: relative; + background: white; + min-height: 10px; + @include o-we-preview-content; - h6 { - color: white; - font-weight: bold; + >img { + cursor: crosshair; + border-top: 1px solid #5A5A5A; + border-bottom: 1px solid #111; } - .o_bg_img_opt_object { - position: relative; - background: white; - min-height: 10px; - @include o-we-preview-content; - - >img { - cursor: crosshair; - border-top: 1px solid #5A5A5A; - border-bottom: 1px solid #111; - } + &:hover .o_focus_point:before { + opacity: 0.5; + } + } - &:hover .o_focus_point:before { - opacity: 0.5; - } + .o_bg_img_opt_ui_info { + animation: fadeInOut 2s ease forwards; + @include o-position-absolute($bottom: 1px, $right: 0); + display: block; + padding: 3px 13px; + background-color: rgba(255, 255, 255, 0.8); + color: #333; + text-align: center; + font-weight: bold; + pointer-events: none; + + span { + font-weight: normal; } - .o_bg_img_opt_ui_info { - animation: fadeInOut 2s ease forwards; - @include o-position-absolute($bottom: 1px, $right: 0); - display: block; - padding: 3px 13px; - background-color: rgba(255, 255, 255, 0.8); - color: #333; - text-align: center; - font-weight: bold; - pointer-events: none; + .o_x { + margin-right: 10px + } + } - span { - font-weight: normal; - } + .grid { + @include o-position-absolute($top: 33.33%, $left: 0); + display: block; + width: 100%; + height: 1px; + background: fade-out(lighten($o-brand-primary, 30%), 0.5); + pointer-events: none; - .o_x { - margin-right: 10px - } + &.grid-2 { + top: 66.66% } - .grid { - @include o-position-absolute($top: 33.33%, $left: 0); - display: block; - width: 100%; - height: 1px; - background: fade-out(lighten($o-brand-primary, 30%), 0.5); - pointer-events: none; - - &.grid-2 { - top: 66.66% - } + &.grid-3 { + top: 0; + left: 33.33%; + width: 1px; + height: 100% + } - &.grid-3 { - top: 0; - left: 33.33%; - width: 1px; - height: 100% - } + &.grid-4 { + top: 0; + left: 66.66%; + width: 1px; + height: 100% + } + } - &.grid-4 { - top: 0; - left: 66.66%; - width: 1px; - height: 100% - } + .o_focus_point { + @include o-position-absolute($top: 0, $left: 0); + width: 30px; + height: 30px; + border: 2px solid white; + border-radius: 100%; + margin-top: -15px; + margin-left: -15px; + box-shadow: 0 0 1px #333; + pointer-events: none; + + &.o_with_transition { + transition: all 0.2s ease 0s; } - .o_focus_point { - @include o-position-absolute($top: 0, $left: 0); - width: 30px; - height: 30px; - border: 2px solid white; - border-radius: 100%; - margin-top: -15px; - margin-left: -15px; - box-shadow: 0 0 1px #333; + &:before { pointer-events: none; - - &.o_with_transition { - transition: all 0.2s ease 0s; - } - - &:before { - pointer-events: none; - content: ""; - display: block; - width: 100px; - height: 100px; - margin-top: -37px; - margin-left: -37px; - border: 1px solid #EAEAEA; - background: rgba(247, 76, 76, 0); - border-radius: 100em; - opacity: 1; - box-shadow: 0 0 0 100em rgba(0, 0, 0, 0.33); - transition: opacity 0.2s ease 0s; - } + content: ""; + display: block; + width: 100px; + height: 100px; + margin-top: -37px; + margin-left: -37px; + border: 1px solid #EAEAEA; + background: rgba(247, 76, 76, 0); + border-radius: 100em; + opacity: 1; + box-shadow: 0 0 0 100em rgba(0, 0, 0, 0.33); + transition: opacity 0.2s ease 0s; } } } } +} - .wysiwyg_multizone.note-editor { - &>.note-toolbar-wrapper .note-toolbar { - height: $o-navbar-height; +.wysiwyg_multizone.note-editor { + &>.note-toolbar-wrapper .note-toolbar { + height: $o-navbar-height; - .btn { - height: $o-navbar-height !important; // force this because of themes - } + .btn { + height: $o-navbar-height !important; // force this because of themes } + } - &>.note-statusbar { - display: none; - } + &>.note-statusbar { + display: none; + } - &>.note-editing-area { - overflow: visible; - } + &>.note-editing-area { + overflow: visible; } +} - .wysiwyg_multizone.note-popover.popover { - border-width: 0 1px; +.wysiwyg_multizone.note-popover.popover { + border-width: 0 1px; - .popover-content .btn { - height: $o-navbar-height; - } + .popover-content .btn { + height: $o-navbar-height; } +} - .wysiwyg_multizone.note-popover, - .wysiwyg_multizone.note-editor>.note-toolbar-wrapper .note-toolbar { - // force this because of themes +.wysiwyg_multizone.note-popover, +.wysiwyg_multizone.note-editor>.note-toolbar-wrapper .note-toolbar { + // force this because of themes + background-color: $o-we-color-dark !important; + + .btn { background-color: $o-we-color-dark !important; + color: $o-we-color-text-normal !important; + border: none !important; + padding: 0.0625rem 0.3125rem !important; - .btn { - background-color: $o-we-color-dark !important; + >.fa { color: $o-we-color-text-normal !important; - border: none !important; - padding: 0.0625rem 0.3125rem !important; + } - >.fa { - color: $o-we-color-text-normal !important; - } + &:hover { + color: $o-we-color-text-light !important; - &:hover { + >.fa { color: $o-we-color-text-light !important; - - >.fa { - color: $o-we-color-text-light !important; - } } + } - &.active { - background-color: $o-we-color-dark !important; - color: $o-we-color-text-light !important; + &.active { + background-color: $o-we-color-dark !important; + color: $o-we-color-text-light !important; - >.fa { - color: $o-we-color-text-light !important; - } + >.fa { + color: $o-we-color-text-light !important; } } + } - >.btn-group { - margin-top: 0 !important; + >.btn-group { + margin-top: 0 !important; - &.show, - .btn-group.show { - .dropdown-toggle { - border-top: 2px solid white !important; - background-color: $o-we-color-darker !important; - color: white !important; - box-shadow: none !important; + &.show, + .btn-group.show { + .dropdown-toggle { + border-top: 2px solid white !important; + background-color: $o-we-color-darker !important; + color: white !important; + box-shadow: none !important; - >.fa, - >span { - color: white !important; - } + >.fa, + >span { + color: white !important; } } } + } - .dropdown-menu { - margin: 0 !important; - border-radius: 0 !important; - background-color: $o-we-color-darker !important; - color: $o-we-color-text-normal !important; - white-space: normal !important; + .dropdown-menu { + margin: 0 !important; + border-radius: 0 !important; + background-color: $o-we-color-darker !important; + color: $o-we-color-text-normal !important; + white-space: normal !important; - .dropdown-item { - width: 100% !important; + .dropdown-item { + width: 100% !important; + color: $o-we-color-text-light !important; + + >.fa { color: $o-we-color-text-light !important; + } - >.fa { - color: $o-we-color-text-light !important; - } + &:hover { + background-color: $o-we-tab-active-bg !important; + color: white !important; - &:hover { - background-color: $o-we-tab-active-bg !important; + >.fa { color: white !important; - - >.fa { - color: white !important; - } } } } + } - .note-para .dropdown-menu { - min-width: 147px !important; - } + .note-para .dropdown-menu { + min-width: 147px !important; + } - .note-style .dropdown-item * { - color: $o-we-color-text-light !important; - } + .note-style .dropdown-item * { + color: $o-we-color-text-light !important; } } diff --git a/addons/website_mass_mailing/views/snippets_templates.xml b/addons/website_mass_mailing/views/snippets_templates.xml index f0fee84ad53..7bc1c4ac2d6 100644 --- a/addons/website_mass_mailing/views/snippets_templates.xml +++ b/addons/website_mass_mailing/views/snippets_templates.xml @@ -3,6 +3,8 @@ <template id="iframe_css_assets_edit" name="CSS assets for wysiwyg iframe content for popup"> <t t-call-assets="web.assets_common" t-js="false"/> <t t-call-assets="web.assets_frontend" t-js="false"/> + <t t-call-assets="web_editor.assets_wysiwyg" t-js="false"/> + <t t-call-assets="website.assets_editor" t-js="false"/> <link rel="stylesheet" href="/website_mass_mailing/static/src/css/website_mass_mailing_popup.css" type="text/css"/> </template> diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 0429f21de5f..112b209bfcd 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -608,9 +608,12 @@ 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 + if order.partner_id.id == request.website.user_id.sudo().partner_id.id: + # If the partner is public, we assign the company of the website + new_values['company_id'] = request.website.company_id.id + lang = request.lang if request.lang in request.website.mapped('language_ids.code') else None if lang: new_values['lang'] = lang diff --git a/addons/website_sale_comparison/static/src/js/website_sale_comparison.js b/addons/website_sale_comparison/static/src/js/website_sale_comparison.js index 306ee1a44f9..48eb44494c4 100644 --- a/addons/website_sale_comparison/static/src/js/website_sale_comparison.js +++ b/addons/website_sale_comparison/static/src/js/website_sale_comparison.js @@ -1,6 +1,7 @@ odoo.define('website_sale_comparison.comparison', function (require) { 'use strict'; +var concurrency = require('web.concurrency'); var core = require('web.core'); var publicWidget = require('web.public.widget'); var utils = require('web.utils'); @@ -30,6 +31,7 @@ var ProductComparison = publicWidget.Widget.extend(VariantMixin, { this.product_data = {}; this.comparelist_product_ids = JSON.parse(utils.get_cookie('comparelist_product_ids') || '[]'); this.product_compare_limit = 4; + this.guard = new concurrency.Mutex(); }, /** * @override @@ -154,6 +156,9 @@ var ProductComparison = publicWidget.Widget.extend(VariantMixin, { * @private */ _addNewProducts: function (product_id) { + this.guard.exec(this._addNewProductsImpl.bind(this, product_id)); + }, + _addNewProductsImpl: function (product_id) { var self = this; $('.o_product_feature_panel').addClass('d-md-block'); if (!_.contains(self.comparelist_product_ids, product_id)) { @@ -161,8 +166,9 @@ var ProductComparison = publicWidget.Widget.extend(VariantMixin, { if (_.has(self.product_data, product_id)){ self._updateContent(); } else { - self._loadProducts([product_id]).then(function () { + return self._loadProducts([product_id]).then(function () { self._updateContent(); + self._updateCookie(); }); } } @@ -189,6 +195,9 @@ var ProductComparison = publicWidget.Widget.extend(VariantMixin, { * @private */ _removeFromComparelist: function (e) { + this.guard.exec(this._removeFromComparelistImpl.bind(this, e)); + }, + _removeFromComparelistImpl: function (e) { this.comparelist_product_ids = _.without(this.comparelist_product_ids, $(e.currentTarget).data('product_product_id')); $(e.currentTarget).parents('.o_product_row').remove(); this._updateCookie(); diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py index f4a02f8937a..dfcc4e4e7de 100644 --- a/addons/website_slides/controllers/main.py +++ b/addons/website_slides/controllers/main.py @@ -379,6 +379,16 @@ class WebsiteSlides(WebsiteProfile): step=self._slides_per_page, url_args=pager_args, scope=page_count if page_count < self._pager_max_pages else self._pager_max_pages) + query_string = None + if category: + query_string = "?search_category=%s" % category.id + elif tag: + query_string = "?search_tag=%s" % tag.id + elif slide_type: + query_string = "?search_slide_type=%s" % slide_type + elif uncategorized: + query_string = "?search_uncategorized=1" + values = { 'channel': channel, 'active_tab': kw.get('active_tab', 'home'), @@ -387,6 +397,7 @@ class WebsiteSlides(WebsiteProfile): 'search_tag': tag, 'search_slide_type': slide_type, 'search_uncategorized': uncategorized, + 'query_string': query_string, 'slide_types': slide_types, 'sorting': actual_sorting, 'search': search, @@ -506,7 +517,7 @@ class WebsiteSlides(WebsiteProfile): @http.route('''/slides/slide/<model("slide.slide", "[('website_id', 'in', (False, current_website_id))]"):slide>''', type='http', auth="public", website=True) def slide_view(self, slide, **kwargs): - if not slide.channel_id.can_access_from_current_website(): + if not slide.channel_id.can_access_from_current_website() or not slide.active: raise werkzeug.exceptions.NotFound() self._set_viewed_slide(slide) @@ -517,6 +528,15 @@ class WebsiteSlides(WebsiteProfile): # sidebar: update with user channel progress values['channel_progress'] = self._get_channel_progress(slide.channel_id, include_quiz=True) + # Allows to have breadcrumb for the previously used filter + values.update({ + 'search_category': slide.category_id.id if kwargs.get('search_category') else None, + 'search_tag': request.env['slide.tag'].browse(int(kwargs.get('search_tag'))) if kwargs.get('search_tag') else None, + 'slide_types': dict(request.env['slide.slide']._fields['slide_type']._description_selection(request.env)) if kwargs.get('search_slide_type') else None, + 'search_slide_type': kwargs.get('search_slide_type'), + 'search_uncategorized': kwargs.get('search_uncategorized') + }) + if kwargs.get('fullscreen') == '1': return request.render("website_slides.slide_fullscreen", values) return request.render("website_slides.slide_main", values) diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py index b0567dcc627..851ec30fd4d 100644 --- a/addons/website_slides/models/slide_slide.py +++ b/addons/website_slides/models/slide_slide.py @@ -135,7 +135,7 @@ class Slide(models.Model): ('video', 'Video'), ('quiz', "Quiz")], string='Type', required=True, - default='document', readonly=True, + default='document', help="The document type will be set automatically based on the document URL and properties (e.g. height and width for presentation and document).") index_content = fields.Text('Transcript') datas = fields.Binary('Content', attachment=True) @@ -553,11 +553,16 @@ class Slide(models.Model): return result def _find_document_data_from_url(self, url): - expr = re.compile(r'^.*((youtu.be/)|(v/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?]*).*') - arg = expr.match(url) - document_id = arg and arg.group(7) or False - if document_id: - return ('youtube', document_id) + url_obj = urls.url_parse(url) + if url_obj.ascii_host == 'youtu.be': + return ('youtube', url_obj.path[1:] if url_obj.path else False) + elif url_obj.ascii_host in ('youtube.com', 'www.youtube.com', 'm.youtube.com'): + v_query_value = url_obj.decode_query().get('v') + if v_query_value: + return ('youtube', v_query_value) + split_path = url_obj.path.split('/') + if len(split_path) >= 3 and split_path[1] in ('v', 'embed'): + return ('youtube', split_path[2]) expr = re.compile(r'(^https:\/\/docs.google.com|^https:\/\/drive.google.com).*\/d\/([^\/]*)') arg = expr.match(url) 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 6fa054b6647..8a648a3cb3b 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 @@ -5,6 +5,7 @@ odoo.define('website_slides.fullscreen', function (require) { var publicWidget = require('web.public.widget'); var core = require('web.core'); + var config = require('web.config'); var QWeb = core.qweb; var session = require('web.session'); @@ -321,6 +322,9 @@ odoo.define('website_slides.fullscreen', function (require) { start: function (){ var self = this; this.on('change:slide', this, this._onChangeSlide); + if (config.device.size_class > config.device.SIZES.MD) { + this._toggleSidebar(); + } return this._super.apply(this, arguments).then(function () { self._onChangeSlide(); // trigger manually once DOM ready, since slide content is not rendered server side }); @@ -540,12 +544,20 @@ odoo.define('website_slides.fullscreen', function (require) { this.sidebar.goNext(); }, /** - * Show or Hide the sidebar + * Called when the sidebar toggle is clicked -> toggles the sidebar visibility. * * @private */ _onClickToggleSidebar: function (ev){ ev.preventDefault(); + this._toggleSidebar(); + }, + /** + * Toggles sidebar visibility. + * + * @private + */ + _toggleSidebar: function () { this.$('.o_wslides_fs_sidebar').toggleClass('o_wslides_fs_sidebar_hidden'); this.$('.o_wslides_fs_toggle_sidebar').toggleClass('active'); }, diff --git a/addons/website_slides/static/src/js/slides_share.js b/addons/website_slides/static/src/js/slides_share.js index 3f921a2229a..d89cdfcb4bf 100644 --- a/addons/website_slides/static/src/js/slides_share.js +++ b/addons/website_slides/static/src/js/slides_share.js @@ -49,49 +49,13 @@ publicWidget.registry.websiteSlidesShare = publicWidget.Widget.extend({ * @param {Object} parent */ start: function (parent) { - var self = this; var defs = [this._super.apply(this, arguments)]; defs.push(new ShareMail(this).attachTo($('.oe_slide_js_share_email'))); - if ($('div#statistic').length) { - this.slideURL = $('div#statistic').attr('slide-url'); - this.socialURLs = { - linkedin: 'https://www.linkedin.com/countserv/count/share?url=', - twitter: 'https://cdn.api.twitter.com/1/urls/count.json?url=', - facebook: 'https://graph.facebook.com/?id=', - }; - } - - _.each(this.socialURLs, function (value, key) { - self._updateStatistics(key, self.slideURL); - }); - return Promise.all(defs); }, //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @private - * @param {string} socialSite - * @param {string} slide_url - */ - _updateStatistics: function (socialSite, slideURL) { - var self = this; - $.ajax({ - url: self.socialURLs[socialSite] + slideURL, - dataType: 'jsonp', - success: function (data) { - var shareCount = (socialSite === 'facebook' ? data.shares : data.count) || 0; - $('#' + socialSite + '-badge').text(shareCount); - $('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#' + socialSite + '-badge').text())); - }, - }); - }, - - //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- @@ -100,14 +64,11 @@ publicWidget.registry.websiteSlidesShare = publicWidget.Widget.extend({ * @param {Object} ev */ _onSlidesSocialShare: function (ev) { - var self = this; ev.preventDefault(); - var key = $(ev.currentTarget).attr('social-key'); var popUpURL = $(ev.currentTarget).attr('href'); var popUp = window.open(popUpURL, 'Share Dialog', 'width=626,height=436'); $(window).on('focus', function () { if (popUp.closed) { - self._updateStatistics(key, self.slide_url); $(window).off('focus'); } }); diff --git a/addons/website_slides/tests/__init__.py b/addons/website_slides/tests/__init__.py index 69bfc7de9e2..466b2b82674 100644 --- a/addons/website_slides/tests/__init__.py +++ b/addons/website_slides/tests/__init__.py @@ -5,3 +5,5 @@ from . import common from . import test_security from . import test_statistics from . import test_karma + +from . import test_from_url
\ No newline at end of file diff --git a/addons/website_slides/tests/test_from_url.py b/addons/website_slides/tests/test_from_url.py new file mode 100644 index 00000000000..dc9a1d6768e --- /dev/null +++ b/addons/website_slides/tests/test_from_url.py @@ -0,0 +1,30 @@ +import odoo.tests + + +class TestFromURL(odoo.tests.TransactionCase): + def test_youtube_urls(self): + urls = { + 'W0JQcpGLSFw': [ + 'https://youtu.be/W0JQcpGLSFw', + 'https://www.youtube.com/watch?v=W0JQcpGLSFw', + 'https://www.youtube.com/watch?v=W0JQcpGLSFw&list=PL1-aSABtP6ACZuppkBqXFgzpNb2nVctZx', + ], + 'vmhB-pt7EfA': [ # id starts with v, it is important + 'https://youtu.be/vmhB-pt7EfA', + 'https://www.youtube.com/watch?feature=youtu.be&v=vmhB-pt7EfA', + 'https://www.youtube.com/watch?v=vmhB-pt7EfA&list=PL1-aSABtP6ACZuppkBqXFgzpNb2nVctZx&index=7', + ], + 'hlhLv0GN1hA': [ + 'https://www.youtube.com/v/hlhLv0GN1hA', + 'https://www.youtube.com/embed/hlhLv0GN1hA', + 'https://m.youtube.com/watch?v=hlhLv0GN1hA' + ], + } + + model = self.env['slide.slide'] + for id, urls in urls.items(): + for url in urls: + with self.subTest(url=url, id=id): + document = model._find_document_data_from_url(url) + self.assertEqual(document[0], 'youtube') + self.assertEqual(document[1], id) diff --git a/addons/website_slides/views/slide_slide_views.xml b/addons/website_slides/views/slide_slide_views.xml index b2dd1585ff0..1a2e45d5119 100644 --- a/addons/website_slides/views/slide_slide_views.xml +++ b/addons/website_slides/views/slide_slide_views.xml @@ -71,13 +71,13 @@ </page> <page string="Document"> <group> - <field name="slide_type" readonly="1"/> + <field name="slide_type"/> <field name="url" - attrs="{'required': [('slide_type', 'in', ('video'))], 'readonly': [('slide_type', 'in', ('document', 'video'))], 'invisible': [('slide_type', 'not in', ('document', 'presentation', 'video'))]}" /> + attrs="{'required': [('slide_type', 'in', ('video'))], 'invisible': [('slide_type', 'not in', ('document', 'presentation', 'video'))]}" /> <field name="document_id" readonly="1" attrs="{'invisible': [('document_id', '=', False)]}"/> <field name="mime_type" readonly="1" attrs="{'invisible': [('slide_type', 'not in', ('document', 'presentation', 'infographic', 'webpage'))]}"/> - <field name="datas" readonly="1" + <field name="datas" attrs="{'invisible': [('slide_type', 'not in', ('document', 'presentation', 'infographic', 'webpage'))]}"/> </group> </page> @@ -93,6 +93,7 @@ <field name="slide_views"/> <field name="public_views"/> <field name="total_views"/> + <field name="completion_time" widget='float_time'/> </group> </group> <label for="index_content"/> diff --git a/addons/website_slides/views/website_slides_templates_course.xml b/addons/website_slides/views/website_slides_templates_course.xml index 5be3e4fb380..fd1e2726270 100644 --- a/addons/website_slides/views/website_slides_templates_course.xml +++ b/addons/website_slides/views/website_slides_templates_course.xml @@ -7,30 +7,34 @@ <div class="container"> <div class="row align-items-center justify-content-between"> <!-- Desktop Mode --> - <nav aria-label="breadcrumb" class="col d-none d-md-flex"> + <nav aria-label="breadcrumb" class="col-md-8 d-none d-md-flex"> <ol class="breadcrumb bg-transparent mb-0 pl-0 py-0"> <li class="breadcrumb-item"> <a href="/slides">Home</a> </li> - <li t-att-class="'breadcrumb-item %s' % ('active' if not search_category and not search_tag and not search_slide_type else '')"> + <t t-set="breadcrumb_class" t-value="'breadcrumb-item %s' % ('active' if not slide else '')" /> + <li t-att-class="'breadcrumb-item %s' % ('active' if not search_category and not search_tag and not search_slide_type and not slide else '')"> <a t-att-href="'/slides/%s' % slug(channel)"><span t-esc="channel.name"/></a> </li> - <li class="breadcrumb-item active" t-att-aria-current="'page' and search_category" t-if="search_category"> + <li t-att-class="breadcrumb_class" t-att-aria-current="'page' and search_category" t-if="search_category"> <a t-att-href="'/slides/%s/category/%s' % (slug(channel), slug(search_category))"><span t-esc="search_category.name"/></a> </li> - <li class="breadcrumb-item active" t-att-aria-current="'page' and search_tag" t-if="search_tag"> + <li t-att-class="breadcrumb_class" t-att-aria-current="'page' and search_tag" t-if="search_tag"> <a t-att-href="'/slides/%s/tag/%s' % (slug(channel), slug(search_tag))"><span t-esc="search_tag.name"/></a> </li> - <li class="breadcrumb-item active" t-att-aria-current="'page' and search_uncategorized" t-if="search_uncategorized"> + <li t-att-class="breadcrumb_class" t-att-aria-current="'page' and search_uncategorized" t-if="search_uncategorized"> <a t-att-href="'/slides/%s?search_uncategorized=1' % (slug(channel))">Uncategorized</a> </li> - <li class="breadcrumb-item active" t-att-aria-current="'page' and search_slide_type" t-if="search_slide_type"> + <li t-att-class="breadcrumb_class" t-att-aria-current="'page' and search_slide_type" t-if="search_slide_type"> <a t-att-href="'/slides/%s?slide_type=%s' % (slug(channel), search_slide_type)"><span t-esc="slide_types[search_slide_type]"/></a> </li> + <li t-if="slide" class="breadcrumb-item active"> + <a t-att-href="'/slides/slide/%s' % slug(slide)"><span t-esc="slide.name"/></a> + </li> </ol> </nav> - <div class="col d-none d-md-flex flex-row align-items-center justify-content-end"> + <div class="col-md-4 d-none d-md-flex flex-row align-items-center justify-content-end"> <!-- search --> <form t-attf-action="/slides/all" role="search" method="get"> <div class="input-group o_wslides_course_nav_search ml-1 position-relative"> @@ -52,21 +56,25 @@ <div class="dropdown-menu"> <a class="dropdown-item" href="/slides">Home</a> + <t t-set="dropdown_class" t-value="'dropdown-item %s' % ('active' if not slide else '')"/> <a t-att-class="'dropdown-item %s' % ('active' if not search_category and not search_tag and not search_slide_type else '')" t-att-href="'/slides/%s' % slug(channel)"> └<span class="ml-1" t-esc="channel.name"/> </a> - <a class="dropdown-item active" t-att-aria-current="'page' and search_category" t-if="search_category" t-att-href="'/slides/%s/category/%s' % (slug(channel), slug(search_category))"> + <a t-att-class="dropdown_class" t-att-aria-current="'page' and search_category" t-if="search_category" t-att-href="'/slides/%s/category/%s' % (slug(channel), slug(search_category))"> └<span class="ml-1" t-esc="search_category.name"/> </a> - <a class="dropdown-item active" t-att-aria-current="'page' and search_tag" t-if="search_tag" t-att-href="'/slides/%s/tag/%s' % (slug(channel), slug(search_tag))"> + <a t-att-class="dropdown_class" t-att-aria-current="'page' and search_tag" t-if="search_tag" t-att-href="'/slides/%s/tag/%s' % (slug(channel), slug(search_tag))"> └<span class="ml-1" t-esc="search_tag.name"/> </a> - <a class="dropdown-item active" t-att-aria-current="'page' and search_uncategorized" t-if="search_uncategorized" t-att-href="'/slides/%s?search_uncategorized=1' % (slug(channel))"> + <a t-att-class="dropdown_class" t-att-aria-current="'page' and search_uncategorized" t-if="search_uncategorized" t-att-href="'/slides/%s?search_uncategorized=1' % (slug(channel))"> └<span class="ml-1">Uncategorized</span> </a> - <a class="dropdown-item active" t-att-aria-current="'page' and search_slide_type" t-if="search_slide_type" t-att-href="'/slides/%s?slide_type=%s' % (slug(channel), search_slide_type)"> + <a t-att-class="dropdown_class" t-att-aria-current="'page' and search_slide_type" t-if="search_slide_type" t-att-href="'/slides/%s?slide_type=%s' % (slug(channel), search_slide_type)"> └<span class="ml-1" t-esc="slide_types[search_slide_type]"/> </a> + <a t-if="slide" class="dropdown-item active" t-att-href="'/slides/slide/%s' % (slug(slide))"> + └<span class="ml-1" t-esc="slide.name"/> + </a> </div> </div> @@ -519,7 +527,7 @@ <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"> + t-attf-href="/slides/slide/#{slug(slide_promoted)}#{query_string}" 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"> @@ -528,7 +536,7 @@ <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)}" + t-attf-href="/slides/slide/#{slug(slide_promoted)}#{query_string}" 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"/> @@ -597,20 +605,20 @@ <t t-if="slide.image"> <t t-set="lesson_image" t-value="'/web/image/slide.slide/%s/image' % slide.id"/> - <a t-if="can_access" t-attf-href="/slides/slide/#{slug(slide)}" t-title="slide.name"> + <a t-if="can_access" t-attf-href="/slides/slide/#{slug(slide)}#{query_string}" t-title="slide.name"> <div class="card-img-top border-bottom" t-attf-style="padding-top: 50%; background-image: url(#{lesson_image}); background-size: cover; background-position:center"/> </a> <div t-else="" class="card-img-top border-bottom" t-attf-style="padding-top: 50%; background-image: url(#{lesson_image}); background-size: cover; background-position:center"/> </t> <t t-else=""> - <a t-if="can_access" t-attf-href="/slides/slide/#{slug(slide)}" t-title="slide.name"> + <a t-if="can_access" t-attf-href="/slides/slide/#{slug(slide)}#{query_string}" t-title="slide.name"> <div class="card-img-top border-bottom o_wslides_gradient" t-attf-style="padding-top: 50%;"/> </a> <div t-else="" class="card-img-top border-bottom o_wslides_gradient" t-attf-style="padding-top: 50%;"/> </t> <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"/> + <a t-if="can_access" class="card-title h5 mb-2o_wslides_desc_truncate_2" t-attf-href="/slides/slide/#{slug(slide)}#{query_string}" 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"> @@ -624,7 +632,7 @@ <div class="o_wslides_desc_truncate_3 font-weight-light oe_no_empty" t-field="slide.description"/> <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/all?channel_tag_id=%s' % tag.id" class="badge badge-light" t-esc="tag.name"/> + <a t-att-href="'/slides/%s/tag/%s' % (slug(slide.channel_id), slug(tag))" class="badge badge-light" t-esc="tag.name"/> </t> </div> </div> diff --git a/addons/website_slides/views/website_slides_templates_homepage.xml b/addons/website_slides/views/website_slides_templates_homepage.xml index 1b822e16037..7b161f446a2 100644 --- a/addons/website_slides/views/website_slides_templates_homepage.xml +++ b/addons/website_slides/views/website_slides_templates_homepage.xml @@ -178,7 +178,7 @@ <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"> + <div id="wrap" class="wrap o_wslides_wrap"> <section class="s_banner bg-900" style="background-image: url("/website_slides/static/src/img/banner_default_all.svg"); 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> diff --git a/addons/website_slides/views/website_slides_templates_lesson.xml b/addons/website_slides/views/website_slides_templates_lesson.xml index 0853c8dccb9..77e154b1ccc 100644 --- a/addons/website_slides/views/website_slides_templates_lesson.xml +++ b/addons/website_slides/views/website_slides_templates_lesson.xml @@ -185,7 +185,7 @@ <div class="row align-items-center my-3"> <div class="col-12 col-md order-2 order-md-1 d-flex"> <div class="d-flex align-items-center"> - <h1 class="h5 my-0"> + <h1 class="h4 my-0"> <t t-call="website_slides.slide_icon"> <t t-set="icon_class">mr-1</t> </t> @@ -245,36 +245,7 @@ <div t-field="slide.html_content"/> </div> </div> - <div class="row" t-if="slide.question_ids"> - <t t-call="website_slides.lesson_content_quiz"/> - </div> - <div class="row mt-3 mb-3"> - <div class="col-12 col-md d-flex align-items-start mb-4 mb-md-0" t-if="len(slide.link_ids)"> - <span t-if="slide.link_ids" class="text-muted font-weight-bold mr-3">External sources</span> - <div class="text-muted mr-auto border-left pl-3"> - <t t-foreach="slide.link_ids" t-as="link"> - <a t-att-href="link.link" t-esc="link.name"/><br /> - </t> - </div> - </div> - <div t-if="slide.channel_id.allow_comment and slide.channel_id.channel_type == 'documentation'" - class="col-12 col-md d-flex align-items-start justify-content-md-end mb-2 mb-md-0"> - <span class="text-muted font-weight-bold mr-3">Rating</span> - <div class="text-muted border-left pl-3"> - <div class="o_wslides_js_slide_like mr-2"> - <span t-att-class="('o_wslides_js_slide_like_up %s') % ('disabled' if not slide.channel_id.can_vote else '')" tabindex="0" data-toggle="popover" t-att-data-slide-id="slide.id"> - <i class="fa fa-thumbs-up fa-1x" role="img" aria-label="Likes" title="Likes"></i> - <span t-esc="slide.likes"/> - </span> - <span t-att-class="('o_wslides_js_slide_like_down ml-3 %s') % ('disabled' if not slide.channel_id.can_vote else '')" tabindex="0" data-toggle="popover" t-att-data-slide-id="slide.id"> - <i class="fa fa-thumbs-down fa-1x" role="img" aria-label="Dislikes" title="Dislikes"></i> - <span t-esc="slide.dislikes"/> - </span> - </div> - </div> - </div> - </div> - <div class="mb-5"> + <div class="my-3"> <ul class="nav nav-tabs o_wslides_lesson_nav" role="tablist"> <li class="nav-item"> <a href="#about" aria-controls="about" class="nav-link active" role="tab" data-toggle="tab"> @@ -362,27 +333,6 @@ </tbody> </table> </div> - <div class="col-12 col-md"> - <table class="table table-sm"> - <tbody> - <tr> - <th colspan="2" class="border-top-0">Share count</th> - </tr> - <tr class="border-top-0"> - <th class="border-top-0"><span id="facebook-badge">0</span></th> - <td class="border-top-0 w-100">Facebook</td> - </tr> - <tr> - <th><span id="twitter-badge">0</span></th> - <td>Twitter</td> - </tr> - <tr> - <th><span id="linkedin-badge"/></th> - <td>LinkedIn</td> - </tr> - </tbody> - </table> - </div> </div> </div> <div role="tabpanel" class="tab-pane fade" t-if="slide.website_published" id="share"> @@ -409,6 +359,35 @@ </div> </div> </div> + <div class="row" t-if="slide.question_ids"> + <t t-call="website_slides.lesson_content_quiz"/> + </div> + <div class="row mt-3 mb-3"> + <div class="col-12 col-md d-flex align-items-start mb-4 mb-md-0" t-if="len(slide.link_ids)"> + <span t-if="slide.link_ids" class="text-muted font-weight-bold mr-3">External sources</span> + <div class="text-muted mr-auto border-left pl-3"> + <t t-foreach="slide.link_ids" t-as="link"> + <a t-att-href="link.link" t-esc="link.name"/><br /> + </t> + </div> + </div> + <div t-if="slide.channel_id.allow_comment and slide.channel_id.channel_type == 'documentation'" + class="col-12 col-md d-flex align-items-start justify-content-md-end mb-2 mb-md-0"> + <span class="text-muted font-weight-bold mr-3">Rating</span> + <div class="text-muted border-left pl-3"> + <div class="o_wslides_js_slide_like mr-2"> + <span t-att-class="('o_wslides_js_slide_like_up %s') % ('disabled' if not slide.channel_id.can_vote else '')" tabindex="0" data-toggle="popover" t-att-data-slide-id="slide.id"> + <i class="fa fa-thumbs-up fa-1x" role="img" aria-label="Likes" title="Likes"></i> + <span t-esc="slide.likes"/> + </span> + <span t-att-class="('o_wslides_js_slide_like_down ml-3 %s') % ('disabled' if not slide.channel_id.can_vote else '')" tabindex="0" data-toggle="popover" t-att-data-slide-id="slide.id"> + <i class="fa fa-thumbs-down fa-1x" role="img" aria-label="Dislikes" title="Dislikes"></i> + <span t-esc="slide.dislikes"/> + </span> + </div> + </div> + </div> + </div> </template> <!-- Slide sub-tempalte: render a quiz serverside. Should be sync with JS qweb template "slide.slide.quiz" --> @@ -427,7 +406,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_questions" t-as="question" - t-att-class="'o_wslides_js_lesson_quiz_question mt-3 mb-4 %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']"> + t-att-class="'o_wslides_js_lesson_quiz_question my-5 %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 5180385f117..7022de0df99 100644 --- a/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml +++ b/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml @@ -3,15 +3,18 @@ <!-- Slide template for the fullscreen mode --> <template id="slide_fullscreen" name="Fullscreen"> + <t t-set="head"> + <link rel="canonical" t-att-href="slide.website_url" /> + </t> <t t-call="website.layout"> <div class="o_wslides_fs_main d-flex flex-column font-weight-light"> <div class="o_wslides_slide_fs_header d-flex flex-shrink-0 text-white"> <div class="d-flex"> - <a class="o_wslides_fs_toggle_sidebar d-flex align-items-center px-3 active" href="#" title="Lessons"> + <a class="o_wslides_fs_toggle_sidebar d-flex align-items-center px-3" href="#" title="Lessons"> <i class="fa fa-bars"/><span class="d-none d-md-inline-block ml-1">Lessons</span> </a> - <a class="o_wslides_fs_review d-flex align-items-center px-3" t-att-href="slide.channel_id.website_url" title="Reviews" t-if="slide.channel_id.allow_comment"> + <a class="o_wslides_fs_review d-flex align-items-center px-3" t-att-href="slide.channel_id.website_url + '?active_tab=review'" title="Reviews" t-if="slide.channel_id.allow_comment"> <i class="fa fa-pencil"/><span class="d-none d-md-inline-block ml-1">Write a review</span> </a> </div> @@ -20,7 +23,7 @@ <i class="fa fa-sign-out d-md-none"/><span class="d-none d-md-inline-block ml-1">Exit Fullscreen</span> </a> <a class="d-flex align-items-center px-3" t-attf-href="/slides/#{slug(slide.channel_id)}"> - <i class="fa fa-sign-out d-md-none"/><span class="d-none d-md-inline-block ml-1">Back to course</span> + <i class="fa fa-home d-md-none"/><span class="d-none d-md-inline-block ml-1">Back to course</span> </a> </div> </div> @@ -28,7 +31,7 @@ <div class="o_wslides_fs_container d-flex position-relative overflow-hidden flex-grow-1"> <div class="o_wslides_fs_content align-items-stretch justify-content-center d-flex flex-grow-1 order-2"></div> - <div class="o_wslides_fs_sidebar text-white flex-shrink-0 order-1"> + <div class="o_wslides_fs_sidebar o_wslides_fs_sidebar_hidden text-white flex-shrink-0 order-1"> <div class="o_wslides_fs_sidebar_content d-flex flex-column px-3 pt-3 h-100"> <div class="o_wslides_fs_sidebar_header mb-3"> <a class="h5 d-block mb-1" t-attf-href="/slides/#{slug(slide.channel_id)}"> diff --git a/addons/website_slides/views/website_slides_templates_profile.xml b/addons/website_slides/views/website_slides_templates_profile.xml index 262c95a7717..212b0ad9993 100644 --- a/addons/website_slides/views/website_slides_templates_profile.xml +++ b/addons/website_slides/views/website_slides_templates_profile.xml @@ -51,12 +51,6 @@ <t t-foreach="course.channel_id.tag_ids" t-as="tag"> <a t-att-href="'/slides/all?channel_tag_id=%s' % tag.id" class="badge badge-light" t-esc="tag.name"/> </t> - <t t-foreach="course.channel_id.tag_ids" t-as="tag"> - <a t-att-href="'/slides/all?channel_tag_id=%s' % tag.id" class="badge badge-light" t-esc="tag.name"/> - </t> - <t t-foreach="course.channel_id.tag_ids" t-as="tag"> - <a t-att-href="'/slides/all?channel_tag_id=%s' % tag.id" class="badge badge-light" t-esc="tag.name"/> - </t> </div> <div class="d-flex align-items-center"> |
