[MERGE] forward port branch saas-16 up to 0af13af5de
This commit is contained in:
@@ -940,7 +940,7 @@ class AccountInvoice(models.Model):
|
||||
total_currency = 0
|
||||
for line in invoice_move_lines:
|
||||
if self.currency_id != company_currency:
|
||||
currency = self.currency_id.with_context(date=self.date or self.date_invoice or fields.Date.context_today(self))
|
||||
currency = self.currency_id.with_context(date=self._get_currency_rate_date() or fields.Date.context_today(self))
|
||||
if not (line.get('currency_id') and line.get('amount_currency')):
|
||||
line['currency_id'] = currency.id
|
||||
line['amount_currency'] = currency.round(line['price'])
|
||||
@@ -1090,7 +1090,7 @@ class AccountInvoice(models.Model):
|
||||
if inv.payment_term_id:
|
||||
totlines = inv.with_context(ctx).payment_term_id.with_context(currency_id=company_currency.id).compute(total, inv.date_invoice)[0]
|
||||
res_amount_currency = total_currency
|
||||
ctx['date'] = inv.date or inv.date_invoice
|
||||
ctx['date'] = inv._get_currency_rate_date()
|
||||
for i, t in enumerate(totlines):
|
||||
if inv.currency_id != company_currency:
|
||||
amount_currency = company_currency.with_context(ctx).compute(t[1], inv.currency_id)
|
||||
@@ -1289,6 +1289,9 @@ class AccountInvoice(models.Model):
|
||||
copy_fields = ['company_id', 'user_id', 'fiscal_position_id']
|
||||
return self._get_refund_common_fields() + self._get_refund_prepare_fields() + copy_fields
|
||||
|
||||
def _get_currency_rate_date(self):
|
||||
return self.date or self.date_invoice
|
||||
|
||||
@api.model
|
||||
def _prepare_refund(self, invoice, date_invoice=None, date=None, description=None, journal_id=None):
|
||||
""" Prepare the dict of values to create the new credit note from the invoice.
|
||||
@@ -1449,7 +1452,7 @@ class AccountInvoiceLine(models.Model):
|
||||
@api.one
|
||||
@api.depends('price_unit', 'discount', 'invoice_line_tax_ids', 'quantity',
|
||||
'product_id', 'invoice_id.partner_id', 'invoice_id.currency_id', 'invoice_id.company_id',
|
||||
'invoice_id.date_invoice')
|
||||
'invoice_id.date_invoice', 'invoice_id.date')
|
||||
def _compute_price(self):
|
||||
currency = self.invoice_id and self.invoice_id.currency_id or None
|
||||
price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
|
||||
@@ -1459,7 +1462,7 @@ class AccountInvoiceLine(models.Model):
|
||||
self.price_subtotal = price_subtotal_signed = taxes['total_excluded'] if taxes else self.quantity * price
|
||||
self.price_total = taxes['total_included'] if taxes else self.price_subtotal
|
||||
if self.invoice_id.currency_id and self.invoice_id.currency_id != self.invoice_id.company_id.currency_id:
|
||||
price_subtotal_signed = self.invoice_id.currency_id.with_context(date=self.invoice_id.date_invoice).compute(price_subtotal_signed, self.invoice_id.company_id.currency_id)
|
||||
price_subtotal_signed = self.invoice_id.currency_id.with_context(date=self.invoice_id._get_currency_rate_date()).compute(price_subtotal_signed, self.invoice_id.company_id.currency_id)
|
||||
sign = self.invoice_id.type in ['in_refund', 'out_refund'] and -1 or 1
|
||||
self.price_subtotal_signed = price_subtotal_signed * sign
|
||||
|
||||
|
||||
@@ -171,6 +171,8 @@ class AccountMove(models.Model):
|
||||
if not move.journal_id.update_posted:
|
||||
raise UserError(_('You cannot modify a posted entry of this journal.\nFirst you should set the journal to allow cancelling entries.'))
|
||||
if self.ids:
|
||||
self.check_access_rights('write')
|
||||
self.check_access_rule('write')
|
||||
self._check_lock_date()
|
||||
self._cr.execute('UPDATE account_move '\
|
||||
'SET state=%s '\
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</p>
|
||||
|
||||
<t t-if="Lines[o.id]">
|
||||
<span t-field="res_company.overdue_msg"/>
|
||||
<span t-field="res_company.with_context(lang=o.lang).overdue_msg"/>
|
||||
<table class="table table-condensed" t-foreach="Lines[o.id]" t-as="currency">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
<field name="crossovered_budget_line" context="{'default_date_from': date_from,'default_date_to': date_to}" colspan="4" nolabel="1" attrs="{'readonly':[('state','!=','draft')]}">
|
||||
<tree string="Budget Lines">
|
||||
<field name="general_budget_id"/>
|
||||
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
|
||||
<field name="analytic_account_id" groups="analytic.group_analytic_accounting" required="1"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
<field name="paid_date" groups="base.group_no_one"/>
|
||||
@@ -111,7 +111,7 @@
|
||||
<group>
|
||||
<field name="general_budget_id"/>
|
||||
<field name="planned_amount" widget="monetary"/>
|
||||
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
|
||||
<field name="analytic_account_id" groups="analytic.group_analytic_accounting" required="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<label for="date_from" string="Period"/>
|
||||
|
||||
@@ -578,7 +578,7 @@ class Import(models.TransientModel):
|
||||
# Check that currency exists
|
||||
currency = self.env['res.currency'].search([('symbol', '=', split_value[currency_index].strip())])
|
||||
if len(currency):
|
||||
return split_value[currency_index + 1 % 2] if not negative else '-' + split_value[currency_index + 1 % 2]
|
||||
return split_value[(currency_index + 1) % 2] if not negative else '-' + split_value[(currency_index + 1) % 2]
|
||||
# Otherwise it is not a float with a currency symbol
|
||||
return False
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class FleetVehicle(models.Model):
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
license_plate = fields.Char(required=True, help='License plate number of the vehicle (i = plate number for a car)')
|
||||
vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False)
|
||||
driver_id = fields.Many2one('res.partner', 'Driver', help='Driver of the vehicle')
|
||||
driver_id = fields.Many2one('res.partner', 'Driver', help='Driver of the vehicle', copy=False)
|
||||
model_id = fields.Many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle')
|
||||
log_fuel = fields.One2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs')
|
||||
log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs')
|
||||
|
||||
@@ -15,6 +15,8 @@ class AccountMoveLine(models.Model):
|
||||
res = super(AccountMoveLine, self).reconcile(writeoff_acc_id=writeoff_acc_id, writeoff_journal_id=writeoff_journal_id)
|
||||
account_move_ids = [l.move_id.id for l in self if float_compare(l.move_id.matched_percentage, 1, precision_digits=5) == 0]
|
||||
if account_move_ids:
|
||||
expense_sheets = self.env['hr.expense.sheet'].search([('account_move_id', 'in', account_move_ids)])
|
||||
expense_sheets = self.env['hr.expense.sheet'].search([
|
||||
('account_move_id', 'in', account_move_ids), ('state', '!=', 'done')
|
||||
])
|
||||
expense_sheets.set_to_paid()
|
||||
return res
|
||||
|
||||
@@ -433,18 +433,20 @@ class HrExpenseSheet(models.Model):
|
||||
|
||||
@api.multi
|
||||
def check_consistency(self):
|
||||
if any(sheet.employee_id != self[0].employee_id for sheet in self):
|
||||
raise UserError(_("Expenses must belong to the same Employee."))
|
||||
|
||||
expense_lines = self.mapped('expense_line_ids')
|
||||
if expense_lines and any(expense.payment_mode != expense_lines[0].payment_mode for expense in expense_lines):
|
||||
raise UserError(_("Expenses must have been paid by the same entity (Company or employee)"))
|
||||
for rec in self:
|
||||
expense_lines = rec.expense_line_ids
|
||||
if not expense_lines:
|
||||
continue
|
||||
if any(expense.employee_id != rec.employee_id for expense in expense_lines):
|
||||
raise UserError(_("Expenses must belong to the same Employee."))
|
||||
if any(expense.payment_mode != expense_lines[0].payment_mode for expense in expense_lines):
|
||||
raise UserError(_("Expenses must have been paid by the same entity (Company or employee)"))
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
self._create_set_followers(vals)
|
||||
sheet = super(HrExpenseSheet, self).create(vals)
|
||||
self.check_consistency()
|
||||
sheet.check_consistency()
|
||||
return sheet
|
||||
|
||||
@api.multi
|
||||
@@ -586,7 +588,7 @@ class HrExpenseSheet(models.Model):
|
||||
return res
|
||||
|
||||
@api.one
|
||||
@api.constrains('expense_line_ids')
|
||||
@api.constrains('expense_line_ids', 'employee_id')
|
||||
def _check_employee(self):
|
||||
employee_ids = self.expense_line_ids.mapped('employee_id')
|
||||
if len(employee_ids) > 1 or (len(employee_ids) == 1 and employee_ids != self.employee_id):
|
||||
|
||||
@@ -373,7 +373,7 @@ elif salary <= 2461.27:
|
||||
<field name="code">Red.Spouse.Net</field>
|
||||
<field name="sequence">148</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = (employee.marital == 'married' and employee.spouse_net_revenue <= 221.0) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA < 0.0)</field>
|
||||
<field name="condition_python">result = (employee.marital == 'married' and employee.spouse_fiscal_status=='with income' and employee.spouse_net_revenue <= 221.0) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA < 0.0)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = min(110.50, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
|
||||
</record>
|
||||
@@ -384,7 +384,7 @@ elif salary <= 2461.27:
|
||||
<field name="code">Red.Spouse.Oth.Net</field>
|
||||
<field name="sequence">149</field>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = (employee.marital == 'married' and not employee.spouse_net_revenue and employee.spouse_other_net_revenue <= 440.0) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA < 0.0)</field>
|
||||
<field name="condition_python">result = (employee.marital == 'married' and employee.spouse_fiscal_status=='with income' and not employee.spouse_net_revenue and employee.spouse_other_net_revenue <= 440.0) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA < 0.0)</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = min(220.0, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
|
||||
</record>
|
||||
|
||||
@@ -215,23 +215,23 @@ class HrEmployee(models.Model):
|
||||
spouse_fiscal_status = fields.Selection([
|
||||
('without income', 'Without Income'),
|
||||
('with income', 'With Income')
|
||||
], string='Tax status for spouse')
|
||||
disabled = fields.Boolean(string="Disabled", help="If the employee is declared disabled by law")
|
||||
disabled_spouse_bool = fields.Boolean(string='Disabled Spouse', help='if recipient spouse is declared disabled by law')
|
||||
disabled_children_bool = fields.Boolean(string='Disabled Children', help='if recipient children is/are declared disabled by law')
|
||||
resident_bool = fields.Boolean(string='Nonresident', help='if recipient lives in a foreign country')
|
||||
disabled_children_number = fields.Integer('Number of disabled children')
|
||||
dependent_children = fields.Integer(compute='_compute_dependent_children', string='Considered number of dependent children')
|
||||
other_dependent_people = fields.Boolean(string="Other Dependent People", help="If other people are dependent on the employee")
|
||||
other_senior_dependent = fields.Integer('# seniors (>=65)', help="Number of seniors dependent on the employee, including the disabled ones")
|
||||
other_disabled_senior_dependent = fields.Integer('# disabled seniors (>=65)')
|
||||
other_juniors_dependent = fields.Integer('# people (<65)', help="Number of juniors dependent on the employee, including the disabled ones")
|
||||
other_disabled_juniors_dependent = fields.Integer('# disabled people (<65)')
|
||||
dependent_seniors = fields.Integer(compute='_compute_dependent_people', string="Considered number of dependent seniors")
|
||||
dependent_juniors = fields.Integer(compute='_compute_dependent_people', string="Considered number of dependent juniors")
|
||||
spouse_net_revenue = fields.Float(string="Spouse Net Revenue", help="Own professional income, other than pensions, annuities or similar income")
|
||||
], string='Tax status for spouse', groups="hr.group_hr_user")
|
||||
disabled = fields.Boolean(string="Disabled", help="If the employee is declared disabled by law", groups="hr.group_hr_user")
|
||||
disabled_spouse_bool = fields.Boolean(string='Disabled Spouse', help='if recipient spouse is declared disabled by law', groups="hr.group_hr_user")
|
||||
disabled_children_bool = fields.Boolean(string='Disabled Children', help='if recipient children is/are declared disabled by law', groups="hr.group_hr_user")
|
||||
resident_bool = fields.Boolean(string='Nonresident', help='if recipient lives in a foreign country', groups="hr.group_hr_user")
|
||||
disabled_children_number = fields.Integer('Number of disabled children', groups="hr.group_hr_user")
|
||||
dependent_children = fields.Integer(compute='_compute_dependent_children', string='Considered number of dependent children', groups="hr.group_hr_user")
|
||||
other_dependent_people = fields.Boolean(string="Other Dependent People", help="If other people are dependent on the employee", groups="hr.group_hr_user")
|
||||
other_senior_dependent = fields.Integer('# seniors (>=65)', help="Number of seniors dependent on the employee, including the disabled ones", groups="hr.group_hr_user")
|
||||
other_disabled_senior_dependent = fields.Integer('# disabled seniors (>=65)', groups="hr.group_hr_user")
|
||||
other_juniors_dependent = fields.Integer('# people (<65)', help="Number of juniors dependent on the employee, including the disabled ones", groups="hr.group_hr_user")
|
||||
other_disabled_juniors_dependent = fields.Integer('# disabled people (<65)', groups="hr.group_hr_user")
|
||||
dependent_seniors = fields.Integer(compute='_compute_dependent_people', string="Considered number of dependent seniors", groups="hr.group_hr_user")
|
||||
dependent_juniors = fields.Integer(compute='_compute_dependent_people', string="Considered number of dependent juniors", groups="hr.group_hr_user")
|
||||
spouse_net_revenue = fields.Float(string="Spouse Net Revenue", help="Own professional income, other than pensions, annuities or similar income", groups="hr.group_hr_user")
|
||||
spouse_other_net_revenue = fields.Float(string="Spouse Other Net Revenue",
|
||||
help='Own professional income which is exclusively composed of pensions, annuities or similar income')
|
||||
help='Own professional income which is exclusively composed of pensions, annuities or similar income', groups="hr.group_hr_user")
|
||||
|
||||
@api.constrains('spouse_fiscal_status', 'spouse_net_revenue', 'spouse_other_net_revenue')
|
||||
def _check_spouse_revenue(self):
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
<field name="disabled_children_bool"/>
|
||||
<field name="disabled_children_number" attrs="{'invisible':[('disabled_children_bool','!=',True)]}" colspan="1" help="number of dependent children declared as disabled"/>
|
||||
<field name="dependent_children" invisible="1"/>
|
||||
<field name="other_dependent_people"/>
|
||||
<field name="other_dependent_people" groups="hr.group_hr_user"/>
|
||||
<field name="other_senior_dependent" attrs="{'invisible':[('other_dependent_people','!=',True)]}"/>
|
||||
<field name="other_disabled_senior_dependent" attrs="{'invisible':[('other_dependent_people','!=',True)]}"/>
|
||||
<field name="other_juniors_dependent" attrs="{'invisible':[('other_dependent_people','!=',True)]}"/>
|
||||
|
||||
@@ -57,11 +57,12 @@ class FleetVehicle(models.Model):
|
||||
for car in self:
|
||||
car.atn = car._get_car_atn(car.acquisition_date, car.car_value, car.fuel_type, car.co2)
|
||||
|
||||
@api.depends('model_id', 'license_plate', 'log_contracts', 'total_depreciated_cost', 'acquisition_date')
|
||||
@api.depends('model_id', 'license_plate', 'log_contracts', 'acquisition_date',
|
||||
'co2_fee', 'log_contracts', 'log_contracts.state', 'log_contracts.recurring_cost_amount_depreciated')
|
||||
def _compute_vehicle_name(self):
|
||||
super(FleetVehicle, self)._compute_vehicle_name()
|
||||
for vehicle in self:
|
||||
acquisition_date = self._get_acquisition_date()
|
||||
acquisition_date = vehicle._get_acquisition_date()
|
||||
vehicle.name += u" \u2022 " + str(round(vehicle.total_depreciated_cost, 2)) + u" \u2022 " + acquisition_date
|
||||
|
||||
def _get_acquisition_date(self):
|
||||
@@ -138,14 +139,14 @@ class FleetVehicleModel(models.Model):
|
||||
model.default_total_depreciated_cost = model.co2_fee + model.default_recurring_cost_amount_depreciated
|
||||
|
||||
@api.multi
|
||||
@api.depends('name', 'brand_id')
|
||||
@api.depends('name', 'brand_id', 'default_total_depreciated_cost')
|
||||
def name_get(self):
|
||||
res = super(FleetVehicleModel, self).name_get()
|
||||
new_res = []
|
||||
for res_item in res:
|
||||
model = self.browse(res_item[0])
|
||||
if model.default_total_depreciated_cost != 0.0:
|
||||
new_res.append((res_item[0], res_item[1] + u" \u2022 " + str(model.default_total_depreciated_cost)))
|
||||
new_res.append((res_item[0], res_item[1] + u" \u2022 " + str(round(model.default_total_depreciated_cost, 2))))
|
||||
else:
|
||||
new_res.append(res_item)
|
||||
return new_res
|
||||
|
||||
@@ -11,8 +11,8 @@ class HrContract(models.Model):
|
||||
domain=lambda self: self._get_available_cars_domain(),
|
||||
default=lambda self: self.env['fleet.vehicle'].search([('driver_id', '=', self.employee_id.address_home_id.id)], limit=1),
|
||||
help="Employee's company car.")
|
||||
car_atn = fields.Float(compute='_compute_car_atn_and_costs', string='ATN Company Car')
|
||||
company_car_total_depreciated_cost = fields.Float(compute='_compute_car_atn_and_costs')
|
||||
car_atn = fields.Float(compute='_compute_car_atn_and_costs', string='ATN Company Car', store=True)
|
||||
company_car_total_depreciated_cost = fields.Float(compute='_compute_car_atn_and_costs', store=True)
|
||||
available_cars_amount = fields.Integer(compute='_compute_available_cars_amount', string='Number of available cars')
|
||||
new_car = fields.Boolean('Request a new car')
|
||||
new_car_model_id = fields.Many2one('fleet.vehicle.model', string="Model", domain=lambda self: self._get_possible_model_domain())
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<field name="car_id" nolabel="1" attrs="{'invisible': [('new_car', '=', True)]}"/>
|
||||
<field name="available_cars_amount" invisible="1"/>
|
||||
</div>
|
||||
<field name="new_car_model_id" attrs="{'invisible': [('new_car', '=', False)]}"/>
|
||||
<field name="new_car_model_id" attrs="{'invisible': ['|', ('transport_mode', '!=', 'company_car'), ('new_car', '=', False)]}"/>
|
||||
<field name="max_unused_cars" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='car_atn']" position="after">
|
||||
|
||||
@@ -59,10 +59,11 @@ class AccountInvoice(models.Model):
|
||||
""" The ISR reference number is 27 characters long. The first 12 of them
|
||||
contain the postal account number of this ISR's issuer, removing the zeros
|
||||
at the beginning and filling the empty places with zeros on the right if it is
|
||||
too short. The 15 other characters contain an internal reference identifying
|
||||
too short. The next 14 characters contain an internal reference identifying
|
||||
the invoice. For this, we use the invoice sequence number, removing each
|
||||
of its non-digit characters, and pad the unused spaces on the left of
|
||||
this number with zeros.
|
||||
this number with zeros. The last character of the ISR number is the result
|
||||
of a recursive modulo 10 on its first 26 characters.
|
||||
"""
|
||||
def _space_isr_number(isr_number):
|
||||
to_treat = isr_number
|
||||
@@ -81,9 +82,9 @@ class AccountInvoice(models.Model):
|
||||
invoice_ref = re.sub('[^\d]', '', record.number)
|
||||
#We only keep the last digits of the sequence number if it is too long
|
||||
invoice_ref = invoice_ref[-l10n_ch_ISR_NUMBER_ISSUER_LENGTH:]
|
||||
internal_ref = invoice_ref.zfill(l10n_ch_ISR_NUMBER_LENGTH - l10n_ch_ISR_NUMBER_ISSUER_LENGTH)
|
||||
internal_ref = invoice_ref.zfill(l10n_ch_ISR_NUMBER_LENGTH - l10n_ch_ISR_NUMBER_ISSUER_LENGTH - 1) # -1 for mod10r check character
|
||||
|
||||
record.l10n_ch_isr_number = invoice_issuer_ref + internal_ref
|
||||
record.l10n_ch_isr_number = mod10r(invoice_issuer_ref + internal_ref)
|
||||
record.l10n_ch_isr_number_spaced = _space_isr_number(record.l10n_ch_isr_number)
|
||||
|
||||
@api.depends('currency_id.name', 'amount_total', 'partner_bank_id.bank_id', 'number', 'partner_bank_id.l10n_ch_postal', 'partner_bank_id.bank_id.l10n_ch_postal_eur', 'partner_bank_id.bank_id.l10n_ch_postal_chf')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from hashlib import sha256
|
||||
from json import dumps
|
||||
import pytz
|
||||
@@ -43,7 +43,7 @@ class pos_session(models.Model):
|
||||
self.ensure_one()
|
||||
date_today = datetime.utcnow()
|
||||
session_start = Datetime.from_string(self.start_at)
|
||||
if not date_today - datetime.timedelta(hours=24) <= session_start:
|
||||
if not date_today - timedelta(hours=24) <= session_start:
|
||||
raise UserError(_("This session has been opened another day. To comply with the French law, you should close sessions on a daily basis. Please close session %s and open a new one.") % self.name)
|
||||
return True
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ or (contract.wage * 0.10))</field>
|
||||
<field name="name">Arrears</field>
|
||||
<field name="category_id" ref="hr_payroll.ALW"/>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = inputs.ARS.amount</field>
|
||||
<field name="amount_python_compute">result = inputs.ARS and inputs.ARS.amount</field>
|
||||
<field eval="0.0" name="amount_fix"/>
|
||||
<field name="sequence" eval="28"/>
|
||||
<field name="note">Generally arrears are fully taxable, but employee may claim exemption u/s 89(1).
|
||||
@@ -185,7 +185,7 @@ Now difference of income tax between payment year and actual year would be allow
|
||||
<field name="category_id" ref="hr_payroll.ALW"/>
|
||||
<field name="condition_select">none</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = inputs.LE.amount</field>
|
||||
<field name="amount_python_compute">result = inputs.LE and inputs.LE.amount</field>
|
||||
<field name="sequence" eval="30"/>
|
||||
<field name="note">Payment by way of leave encashment received by Central and State Govt.employees at the time of retirement in respect of the period of earned leave at credit is fully exempt. In case of other employees, the exemption is to be limited to minimum of all below:
|
||||
1.The actual amount received
|
||||
@@ -199,7 +199,7 @@ Now difference of income tax between payment year and actual year would be allow
|
||||
<field name="name">Performance Incentive</field>
|
||||
<field name="category_id" ref="hr_payroll.ALW"/>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = inputs.PERF.amount</field>
|
||||
<field name="amount_python_compute">result = inputs.PERF and inputs.PERF.amount</field>
|
||||
<field name="sequence" eval="31"/>
|
||||
<field name="note">This would be fully taxable based on incentive.</field>
|
||||
</record>
|
||||
@@ -209,7 +209,7 @@ Now difference of income tax between payment year and actual year would be allow
|
||||
<field name="name">Bonus</field>
|
||||
<field name="category_id" ref="hr_payroll.ALW"/>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = inputs.BNS.amount</field>
|
||||
<field name="amount_python_compute">result = inputs.BNS and inputs.BNS.amount</field>
|
||||
<field name="sequence" eval="41"/>
|
||||
<field name="note">This would be fully taxable based on Bonus.</field>
|
||||
</record>
|
||||
@@ -229,7 +229,7 @@ Now difference of income tax between payment year and actual year would be allow
|
||||
<field name="name">Medical Reimbursement</field>
|
||||
<field name="category_id" ref="hr_payroll.ALW"/>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = inputs.MR.amount</field>
|
||||
<field name="amount_python_compute">result = inputs.MR and inputs.MR.amount</field>
|
||||
<field name="sequence" eval="32"/>
|
||||
<field name="note">This component is on-taxable up to 15000 per year (or Rs 1250 per month) on producing medical bills.</field>
|
||||
</record>
|
||||
@@ -250,7 +250,7 @@ Now difference of income tax between payment year and actual year would be allow
|
||||
<field name="name">Periodical Journals</field>
|
||||
<field name="category_id" ref="hr_payroll.ALW"/>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = inputs.PJ.amount</field>
|
||||
<field name="amount_python_compute">result = inputs.PJ and inputs.PJ.amount</field>
|
||||
<field name="sequence" eval="34"/>
|
||||
<field name="note">Some employers may provide component for buying magazines, journals and books as a part of knowledge enhancement for business growth.This part would become non taxable on providing original bills.</field>
|
||||
</record>
|
||||
@@ -280,7 +280,7 @@ Now difference of income tax between payment year and actual year would be allow
|
||||
<field name="name">Telephone Reimbursement</field>
|
||||
<field name="category_id" ref="hr_payroll.ALW"/>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = inputs.TR.amount</field>
|
||||
<field name="amount_python_compute">result = inputs.TR and inputs.TR.amount</field>
|
||||
<field name="sequence" eval="36"/>
|
||||
<field name="note">In some of the cases, companies may provide a component for telephone bills.Employees may provide actual phone usage bills to reimburse this component and make it non-taxable.
|
||||
</field>
|
||||
@@ -300,7 +300,7 @@ Now difference of income tax between payment year and actual year would be allow
|
||||
<field name="name">Car Expenses Reimbursement</field>
|
||||
<field name="category_id" ref="hr_payroll.ALW"/>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = inputs.CEI.amount</field>
|
||||
<field name="amount_python_compute">result = inputs.CEI and inputs.CEI.amount</field>
|
||||
<field name="sequence" eval="38"/>
|
||||
<field name="note">In case company provides component for this and employee use self owned car for official and personal purposes, Rs 1800 per month would be non-taxable on showing bills for fuel or can maintenance. This amount would be Rs 2400 in case car is more capacity than 1600cc.</field>
|
||||
</record>
|
||||
@@ -310,7 +310,7 @@ Now difference of income tax between payment year and actual year would be allow
|
||||
<field name="name">Internet Expense</field>
|
||||
<field name="category_id" ref="hr_payroll.ALW"/>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = inputs.IE.amount</field>
|
||||
<field name="amount_python_compute">result = inputs.IE and inputs.IE.amount</field>
|
||||
<field name="sequence" eval="39"/>
|
||||
<field name="note">Employer may also provide reimbursement of internet expenses and thus this would become non taxable.</field>
|
||||
</record>
|
||||
@@ -463,7 +463,7 @@ or - (contract.wage * contract.voluntary_provident_fund / 100))</field>
|
||||
<field name="category_id" ref="hr_payroll.DED"/>
|
||||
<field name="condition_select">none</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute">result = - (inputs.LAI.amount)</field>
|
||||
<field name="amount_python_compute">result = inputs.LAI and - (inputs.LAI.amount)</field>
|
||||
<field name="register_id" ref="hr_leave_availed_register"/>
|
||||
<field name="sequence" eval="180"/>
|
||||
</record>
|
||||
|
||||
@@ -179,20 +179,17 @@ tag_lu_177,I.B.5. Tabacs fabriqués,taxes
|
||||
tag_lu_178,Base - Vente Biens Tabacs - Extracommunautaire,taxes
|
||||
tag_lu_179,Base - Vente Biens Tabacs - Intracommunautaire,taxes
|
||||
tag_lu_180,Base - Vente Biens Tabacs - Pays,taxes
|
||||
tag_lu_181,I.B.6.a) Livraisons subséquentes à des acquisitions intracommunautaires dans
|
||||
le cadre d'opérations triangulaires, lorsque le destinataire identifié à la TVA dans l'Etat membre de destination des biens y est le redevable de la taxe (4),taxes
|
||||
tag_lu_181,"I.B.6.a) Livraisons subséquentes à des acquisitions intracommunautaires dans le cadre d'opérations triangulaires, lorsque le destinataire identifié à la TVA dans l'Etat membre de destination des biens y est le redevable de la taxe (4)",taxes
|
||||
tag_lu_182,Base - Vente Biens – Triangulaire Intracommunautaire,taxes
|
||||
tag_lu_183,I.B.6.d) Autres opérations réalisées (imposables) à l'étranger,taxes
|
||||
tag_lu_184,Base - Vente Prestations - Extracommunautaire,taxes
|
||||
tag_lu_185,I.B.6.c) Opérations réalisées dans le cadre du régime particulier de l'article 56sexies,taxes
|
||||
tag_lu_186,I.B.7. Opérations à l'intérieur du pays pour lesquelles le preneur est le redevable,taxes
|
||||
tag_lu_187,I.B.6.b)1) Prestations de services à des identifiés à la TVA dans un autre
|
||||
Etat membre non exonérées dans l'Etat membre du preneur redevable (art.17/1/b) (5),taxes
|
||||
tag_lu_187,I.B.6.b)1) Prestations de services à des identifiés à la TVA dans un autre Etat membre non exonérées dans l'Etat membre du preneur redevable (art.17/1/b) (5),taxes
|
||||
tag_lu_188,Base - Vente Prestations - Intracommunautaire,taxes
|
||||
tag_lu_189,I.B.6.b)2) Prestations de services à des identifiés à la TVA dans un autre
|
||||
Etat membre exonérées dans l'Etat membre du preneur (art.17/1/b),taxes
|
||||
tag_lu_189,I.B.6.b)2) Prestations de services à des identifiés à la TVA dans un autre Etat membre exonérées dans l'Etat membre du preneur (art.17/1/b),taxes
|
||||
tag_lu_190,Base - Vente Prestations - Intracommunautaire exonérées dans l'état membre,taxes
|
||||
tag_lu_191,I.B.1. Livraisons intracommunautaires de biens à des personnes identifiées à la TVA dans unautre Etat membre (art.43/1/d e et f) (3),taxes
|
||||
tag_lu_191,I.B.1. Livraisons intracommunautaires de biens à des personnes identifiées à la TVA dans un autre Etat membre (art.43/1/d e et f) (3),taxes
|
||||
tag_lu_192,Base - Vente Biens - Intracommunautaire,taxes
|
||||
tag_lu_193,I.C. Chiffre d'affaires imposable,taxes
|
||||
tag_lu_194,II.A. Ventilation du chiffre d'affaires imposable – base,taxes
|
||||
@@ -598,4 +595,4 @@ tag_lu_594,Taxe - Investissement Prestations 17% - Intracommunautaire (-),taxes
|
||||
tag_lu_595,Taxe - Investissement Prestations 3% - Intracommunautaire (-),taxes
|
||||
tag_lu_596,Taxe - Investissement Prestations 6% - Intracommunautaire (-),taxes
|
||||
tag_lu_597,Taxe - Investissement Prestations 8% - Intracommunautaire (-),taxes
|
||||
tag_lu_598,Base - Sans Taxe,taxes
|
||||
tag_lu_598,Base - Sans Taxe,taxes
|
||||
|
||||
|
@@ -15,6 +15,12 @@ class IrModel(models.Model):
|
||||
help="Whether this model supports messages and notifications.",
|
||||
)
|
||||
|
||||
def unlink(self):
|
||||
# Delete followers for models that will be unlinked.
|
||||
query = "DELETE FROM mail_followers WHERE res_model IN %s"
|
||||
self.env.cr.execute(query, [tuple(self.mapped('model'))])
|
||||
return super(IrModel, self).unlink()
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
if self and 'is_mail_thread' in vals:
|
||||
|
||||
@@ -846,6 +846,9 @@ class Message(models.Model):
|
||||
('channel_ids', 'in', email_channels.ids),
|
||||
('email', '!=', self_sudo.author_id.email or self_sudo.email_from),
|
||||
])._notify(self, force_send=force_send, send_after_commit=send_after_commit, user_signature=user_signature)
|
||||
|
||||
notif_users.mapped('partner_id')._notify_by_chat(self)
|
||||
|
||||
channels_sudo._notify(self)
|
||||
|
||||
# Discard cache, because child / parent allow reading and therefore
|
||||
|
||||
@@ -2150,7 +2150,7 @@ class MailThread(models.AbstractModel):
|
||||
new_channels.setdefault(header_follower.channel_id.id, set()).add(new_subtype.id)
|
||||
|
||||
# add followers coming from res.users relational fields that are tracked
|
||||
to_add_users = self.env['res.users'].sudo().browse([values[name] for name in user_field_lst if values.get(name)])
|
||||
to_add_users = self.env['res.users'].sudo().browse([values[name] for name in user_field_lst if values.get(name)]).filtered(lambda u: u.partner_id.active)
|
||||
for partner in to_add_users.mapped('partner_id'):
|
||||
new_partners.setdefault(partner.id, None)
|
||||
|
||||
|
||||
@@ -127,6 +127,21 @@ var FieldMany2ManyTagsEmail = M2MTags.extend({
|
||||
return _super.apply(self, arguments);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Override for res.partner
|
||||
* name_get is dynamic (based on context) while display_name is static
|
||||
* (stored)
|
||||
*/
|
||||
get_render_data: function(ids){
|
||||
this.dataset.cancel_read();
|
||||
return this.dataset.name_get(ids).then(function (names) {
|
||||
return _.map(names, function(name) {
|
||||
return {id: name[0], display_name: name[1]};
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
field_registry.add('many2many_tags_email', FieldMany2ManyTagsEmail);
|
||||
|
||||
@@ -125,9 +125,11 @@
|
||||
<field name="name">Work Orders Performance</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">mrp.workorder</field>
|
||||
<field name="domain">[('state','=','done')]</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="context">{'search_default_done': True}</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">graph,pivot,tree,form,gantt</field>
|
||||
<field name="search_view_id" ref="view_mrp_production_work_order_search"/>
|
||||
</record>
|
||||
|
||||
<!-- Workcenter Kanban view-->
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
<tree>
|
||||
<field name="date_start"/>
|
||||
<field name="date_end"/>
|
||||
<field name="duration" widget="float_time"/>
|
||||
<field name="duration" widget="float_time" sum="Total duration"/>
|
||||
<field name="user_id"/>
|
||||
<field name="loss_id" string="Efficiency"/>
|
||||
</tree>
|
||||
|
||||
@@ -206,7 +206,7 @@ exports.PosModel = Backbone.Model.extend({
|
||||
},{
|
||||
model: 'account.tax',
|
||||
fields: ['name','amount', 'price_include', 'include_base_amount', 'amount_type', 'children_tax_ids'],
|
||||
domain: null,
|
||||
domain: function(self) {return [['company_id', '=', self.company && self.company.id || false]]},
|
||||
loaded: function(self, taxes){
|
||||
self.taxes = taxes;
|
||||
self.taxes_by_id = {};
|
||||
|
||||
@@ -2030,6 +2030,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
|
||||
}
|
||||
|
||||
order.initialize_validation_date();
|
||||
order.finalized = true;
|
||||
|
||||
if (order.is_to_invoice()) {
|
||||
var invoiced = this.pos.push_and_invoice_order(order);
|
||||
|
||||
@@ -108,7 +108,7 @@ class Pricelist(models.Model):
|
||||
"""
|
||||
self.ensure_one()
|
||||
if not date:
|
||||
date = self._context.get('date') or fields.Date.today()
|
||||
date = self._context.get('date') or fields.Date.context_today(self)
|
||||
if not uom_id and self._context.get('uom'):
|
||||
uom_id = self._context['uom']
|
||||
if uom_id:
|
||||
|
||||
@@ -15,7 +15,7 @@ class CustomerPortal(CustomerPortal):
|
||||
def _prepare_portal_layout_values(self):
|
||||
values = super(CustomerPortal, self)._prepare_portal_layout_values()
|
||||
partner = request.env.user.partner_id
|
||||
values['purchase_count'] = request.env['purchase.order'].search_count([
|
||||
values['purchase_count'] = request.env['purchase.order'].sudo().search_count([
|
||||
'|',
|
||||
('message_partner_ids', 'child_of', [partner.commercial_partner_id.id]),
|
||||
('partner_id', 'child_of', [partner.commercial_partner_id.id]),
|
||||
|
||||
@@ -76,7 +76,7 @@ class AccountInvoiceLine(models.Model):
|
||||
prod_moves = [m for m in moves if m.product_id.id == product_id]
|
||||
prod_qty_done = factor * qty_done
|
||||
prod_quantity = factor * quantity
|
||||
average_price_unit += self._compute_average_price(prod_qty_done, prod_quantity, prod_moves)
|
||||
average_price_unit += factor * self._compute_average_price(prod_qty_done, prod_quantity, prod_moves)
|
||||
price_unit = average_price_unit or price_unit
|
||||
price_unit = self.product_id.uom_id._compute_price(price_unit, self.uom_id)
|
||||
return price_unit
|
||||
|
||||
@@ -608,7 +608,7 @@ class WebClient(http.Controller):
|
||||
def version_info(self):
|
||||
return odoo.service.common.exp_version()
|
||||
|
||||
@http.route('/web/tests', type='http', auth="none")
|
||||
@http.route('/web/tests', type='http', auth="user")
|
||||
def test_suite(self, mod=None, **kwargs):
|
||||
return request.render('web.qunit_suite')
|
||||
|
||||
|
||||
@@ -941,7 +941,7 @@ var ActionManager = Widget.extend({
|
||||
});
|
||||
});
|
||||
},
|
||||
ir_actions_act_url: function (action) {
|
||||
ir_actions_act_url: function (action, options) {
|
||||
var url = action.url;
|
||||
if (session.debug && url && url.length && url[0] === '/') {
|
||||
url = $.param.querystring(url, {debug: session.debug});
|
||||
@@ -952,6 +952,7 @@ var ActionManager = Widget.extend({
|
||||
return $.Deferred(); // The action is finished only when the redirection is done
|
||||
} else {
|
||||
window.open(url, '_blank');
|
||||
options.on_close();
|
||||
}
|
||||
return $.when();
|
||||
},
|
||||
|
||||
@@ -139,6 +139,9 @@
|
||||
// Filters
|
||||
.o_filters_menu {
|
||||
.o-selected-li;
|
||||
|
||||
max-height: ~"calc(100vh - 125px)";
|
||||
overflow: auto;
|
||||
|
||||
.o_filter_condition {
|
||||
.o-search-options-dropdown-custom-li;
|
||||
@@ -175,6 +178,9 @@
|
||||
// Group By
|
||||
.o_group_by_menu {
|
||||
.o-selected-li;
|
||||
|
||||
max-height: ~"calc(100vh - 125px)";
|
||||
overflow: auto;
|
||||
|
||||
.divider {display: none;} // hide the divider by default (no group)
|
||||
.o_add_group { // add button
|
||||
@@ -188,6 +194,9 @@
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
.o-selected-li;
|
||||
|
||||
max-height: ~"calc(100vh - 125px)";
|
||||
overflow: auto;
|
||||
|
||||
.divider {display: none;} // hide the divider by default (no favorite)
|
||||
.o-searchview-custom-private, .o-searchview-custom-public {
|
||||
|
||||
@@ -221,7 +221,8 @@ var FieldTextHtml = AbstractField.extend({
|
||||
start: function () {
|
||||
var self = this;
|
||||
|
||||
this.loaded = false;
|
||||
this.editorLoadedDeferred = $.Deferred();
|
||||
this.contentLoadedDeferred = $.Deferred();
|
||||
this.callback = _.uniqueId('FieldTextHtml_');
|
||||
window.odoo[this.callback+"_editor"] = function (EditorBar) {
|
||||
setTimeout(function () {
|
||||
@@ -312,8 +313,6 @@ var FieldTextHtml = AbstractField.extend({
|
||||
old_initialize_content: function () {
|
||||
this.$el.closest('.modal-body').css('max-height', 'none');
|
||||
this.$iframe = this.$el.find('iframe');
|
||||
// deactivate any button to avoid saving a not ready iframe
|
||||
$('.o_cp_buttons, .o_statusbar_buttons').find('button').addClass('o_disabled').attr('disabled', true);
|
||||
this.document = null;
|
||||
this.$body = $();
|
||||
this.$content = $();
|
||||
@@ -328,9 +327,7 @@ var FieldTextHtml = AbstractField.extend({
|
||||
this.$content = this.$body.find("#editable_area");
|
||||
this.render();
|
||||
this.add_button();
|
||||
this.loaded = true;
|
||||
// reactivate all the buttons when the field's content (the iframe) is loaded
|
||||
$('.o_cp_buttons, .o_statusbar_buttons').find('button').removeClass('o_disabled').attr('disabled', false);
|
||||
this.contentLoadedDeferred.resolve();
|
||||
setTimeout(self.resize, 0);
|
||||
},
|
||||
on_editor_loaded: function (EditorBar) {
|
||||
@@ -339,6 +336,7 @@ var FieldTextHtml = AbstractField.extend({
|
||||
if (this.value && window.odoo[self.callback+"_updown"] && !(this.$content.html()||"").length) {
|
||||
this.render();
|
||||
}
|
||||
this.editorLoadedDeferred.resolve();
|
||||
setTimeout(function () {
|
||||
setTimeout(self.resize,0);
|
||||
}, 0);
|
||||
@@ -395,20 +393,25 @@ var FieldTextHtml = AbstractField.extend({
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set the value when the widget is fully loaded (content + editor).
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
commitChanges: function () {
|
||||
if (!this.loaded || this.mode === 'readonly') {
|
||||
var self = this;
|
||||
var result = this._super.bind(this, arguments);
|
||||
if (this.mode === 'readonly') {
|
||||
return;
|
||||
}
|
||||
// switch to WYSIWYG mode if currently in code mode to get all changes
|
||||
if (config.debug && this.mode === 'edit' && this.editor.rte) {
|
||||
var layoutInfo = this.editor.rte.editable().data('layoutInfo');
|
||||
$.summernote.pluginEvents.codeview(undefined, undefined, layoutInfo, false);
|
||||
}
|
||||
this.editor.snippetsMenu && this.editor.snippetsMenu.cleanForSave();
|
||||
this._setValue(this.$content.html());
|
||||
return this._super.apply(this, arguments);
|
||||
return $.when(this.contentLoadedDeferred, this.editorLoadedDeferred, result).then(function () {
|
||||
// switch to WYSIWYG mode if currently in code mode to get all changes
|
||||
if (config.debug && self.editor.rte) {
|
||||
var layoutInfo = self.editor.rte.editable().data('layoutInfo');
|
||||
$.summernote.pluginEvents.codeview(undefined, undefined, layoutInfo, false);
|
||||
}
|
||||
self.editor.snippetsMenu && self.editor.snippetsMenu.cleanForSave();
|
||||
self._setValue(self.$content.html());
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,60 @@ var eventHandler = $.summernote.eventHandler;
|
||||
var renderer = $.summernote.renderer;
|
||||
var options = $.summernote.options;
|
||||
|
||||
// Browser-unify execCommand
|
||||
var oldJustify = {};
|
||||
_.each(['Left', 'Right', 'Full', 'Center'], function (align) {
|
||||
oldJustify[align] = editor['justify' + align];
|
||||
editor['justify' + align] = function ($editable, value) {
|
||||
// Before calling the standard function, check all elements which have
|
||||
// an 'align' attribute and mark them with their value
|
||||
var $align = $editable.find('[align]');
|
||||
_.each($align, function (el) {
|
||||
var $el = $(el);
|
||||
$el.data('__align', $el.attr('align'));
|
||||
});
|
||||
|
||||
// Call the standard function
|
||||
oldJustify[align].apply(this, arguments);
|
||||
|
||||
// Then:
|
||||
|
||||
// Remove the text-align of elements which lost the 'align' attribute
|
||||
var $newAlign = $editable.find('[align]');
|
||||
$align.not($newAlign).css('text-align', '');
|
||||
|
||||
// Transform the 'align' attribute into the 'text-align' css
|
||||
// property for elements which received the 'align' attribute or whose
|
||||
// 'align' attribute changed
|
||||
_.each($newAlign, function (el) {
|
||||
var $el = $(el);
|
||||
|
||||
var oldAlignValue = $align.data('__align');
|
||||
var alignValue = $el.attr('align');
|
||||
if (oldAlignValue === alignValue) {
|
||||
// If the element already had an 'align' attribute and that it
|
||||
// did not changed, do nothing (compatibility)
|
||||
return;
|
||||
}
|
||||
|
||||
$el.removeAttr('align');
|
||||
$el.css('text-align', alignValue);
|
||||
|
||||
// Note the first step (removing the text-align of elemnts which
|
||||
// lost the 'align' attribute) is kinda the same as this one, but
|
||||
// this one handles the elements which have been edited with chrome
|
||||
// or with this new system
|
||||
$el.find('*').css('text-align', '');
|
||||
});
|
||||
|
||||
// Unmark the elements
|
||||
$align.removeData('__align');
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// Add methods to summernote
|
||||
|
||||
dom.hasContentAfter = function (node) {
|
||||
var next;
|
||||
if (dom.isEditable(node)) return;
|
||||
@@ -2289,26 +2342,6 @@ eventHandler.modules.popover.update = function ($popover, oStyle, isAirMode) {
|
||||
}
|
||||
};
|
||||
|
||||
// override summernote clipboard functionality
|
||||
eventHandler.modules.clipboard.attach = function (layoutInfo) {
|
||||
var $editable = layoutInfo.editable();
|
||||
$editable.on('paste', function (e) {
|
||||
var clipboardData = ((e.originalEvent || e).clipboardData || window.clipboardData);
|
||||
// Change nothing if pasting html (copy from text editor / web / ...) or
|
||||
// if clipboardData is not available (IE / ...)
|
||||
if (clipboardData && clipboardData.types && clipboardData.types.length === 1 && clipboardData.types[0] === "text/plain") {
|
||||
e.preventDefault();
|
||||
$editable.data('NoteHistory').recordUndo($editable); // FIXME
|
||||
var pastedText = clipboardData.getData("text/plain");
|
||||
// Try removing linebreaks which are not really linebreaks (in a PDF,
|
||||
// when a sentence goes over the next line, copying it considers it
|
||||
// a linebreak for example).
|
||||
var formattedText = pastedText.replace(/([\w-])\r?\n([\w-])/g, "$1 $2").trim();
|
||||
document.execCommand("insertText", false, formattedText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var fn_attach = eventHandler.attach;
|
||||
eventHandler.attach = function (oLayoutInfo, options) {
|
||||
var $editable = oLayoutInfo.editor().hasClass('note-editable') ? oLayoutInfo.editor() : oLayoutInfo.editor().find('.note-editable');
|
||||
|
||||
@@ -4,6 +4,7 @@ odoo.define('web_editor.web_editor_tests', function (require) {
|
||||
var FormView = require('web.FormView');
|
||||
var testUtils = require('web.test_utils');
|
||||
var core = require('web.core');
|
||||
var web_editor = require('web_editor.editor');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
@@ -191,7 +192,7 @@ QUnit.test('field htmlsimple does not crash when commitChanges is called in mode
|
||||
QUnit.test('html_frame does not crash when saving in readonly', function (assert) {
|
||||
// The 'Save' action may be triggered even in readonly (e.g. when clicking
|
||||
// on a button in the form view)
|
||||
assert.expect(0);
|
||||
assert.expect(2);
|
||||
|
||||
var form = testUtils.createView({
|
||||
View: FormView,
|
||||
@@ -203,10 +204,13 @@ QUnit.test('html_frame does not crash when saving in readonly', function (assert
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
mockRPC: function (route) {
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method) {
|
||||
assert.step(args.method);
|
||||
}
|
||||
if (_.str.startsWith(route, '/test')) {
|
||||
// manually call the callback to simulate that the iframe has
|
||||
// been correctly loaded
|
||||
// been loaded (note: just the content, not the editor)
|
||||
window.odoo[$.deparam(route).callback + '_content'].call();
|
||||
return $.when();
|
||||
}
|
||||
@@ -215,7 +219,109 @@ QUnit.test('html_frame does not crash when saving in readonly', function (assert
|
||||
});
|
||||
|
||||
form.saveRecord(); // before the fix done in this commit, it crashed here
|
||||
|
||||
assert.verifySteps(['read']);
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('html_frame does not crash when saving in edit mode (editor not loaded)', function (assert) {
|
||||
// The 'Save' action may be triggered when saving in edit mode very fast
|
||||
// so that the editor may be not loaded, even though the content is!
|
||||
assert.expect(2);
|
||||
|
||||
var form = testUtils.createView({
|
||||
View: FormView,
|
||||
model: 'mass.mailing',
|
||||
data: this.data,
|
||||
arch: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="body" widget="html_frame" options="{\'editor_url\': \'/test\'}"/>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method) {
|
||||
assert.step(args.method);
|
||||
}
|
||||
if (_.str.startsWith(route, '/test')) {
|
||||
// manually call the callback to simulate that the iframe has
|
||||
// been partially loaded (just the content, not the editor)
|
||||
window.odoo[$.deparam(route).callback + '_content']();
|
||||
return $.when();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
form.$('input').val('trululu').trigger('input');
|
||||
form.$buttons.find('.o_form_button_save').click(); // crash without editor fully loaded
|
||||
|
||||
assert.verifySteps(['read']);
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('html_frame saving in edit mode (editor and content fully loaded)', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(4);
|
||||
|
||||
var editorBar = new web_editor.Class();
|
||||
var loadDeferred = $.Deferred();
|
||||
var writeDeferred = $.Deferred();
|
||||
|
||||
var form = testUtils.createView({
|
||||
View: FormView,
|
||||
model: 'mass.mailing',
|
||||
data: this.data,
|
||||
arch: '<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<field name="display_name"/>' +
|
||||
'<field name="body" widget="html_frame" options="{\'editor_url\': \'/test\'}"/>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method) {
|
||||
assert.step(args.method);
|
||||
if (args.method === 'write') {
|
||||
writeDeferred.resolve();
|
||||
}
|
||||
}
|
||||
if (_.str.startsWith(route, '/test')) {
|
||||
// manually call the callback to simulate that the iframe has
|
||||
// been fully loaded (content + editor)
|
||||
var callback = $.deparam(route).callback;
|
||||
return loadDeferred.then(function () {
|
||||
var contentCallback = window.odoo[callback + '_content'];
|
||||
var editorCallback = window.odoo[callback + '_editor'];
|
||||
if (editorCallback && contentCallback) {
|
||||
contentCallback();
|
||||
editorCallback(editorBar);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
form.$('input').val('trululu').trigger('input');
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
|
||||
loadDeferred.resolve(); // simulate late loading of html frame
|
||||
|
||||
assert.strictEqual(form.$('.o_field_char').val(), 'trululu',
|
||||
"should have saved the char field text");
|
||||
|
||||
writeDeferred.then( function () { // html_frame is async with write
|
||||
assert.verifySteps(['read', 'write']);
|
||||
form.destroy();
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//form[@id='registration_form']" position="attributes">
|
||||
<attribute name="t-if">event.event_ticket_ids</attribute>
|
||||
<attribute name="t-if">event.event_ticket_ids and not all([ticket.is_expired for ticket in event.event_ticket_ids])</attribute>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ odoo.define('website_sale.website_sale', function (require) {
|
||||
}
|
||||
else {
|
||||
$q.parents('li:first').addClass("hidden");
|
||||
$('a[href^="/shop/checkout"]').addClass("hidden");
|
||||
$('a[href*="/shop/checkout"]').addClass("hidden");
|
||||
}
|
||||
|
||||
$q.html(data.cart_quantity).hide().fadeIn(600);
|
||||
|
||||
@@ -12,3 +12,4 @@ List of contributors:
|
||||
|
||||
Laurent Destailleur ldestailleur@teclib.com https://github.com/eldy
|
||||
David Halgand dhalgand@teclib.com https://github.com/halgandd
|
||||
Guillaume Masson gmasson@teclib.com https://github.com/metaminux
|
||||
|
||||
@@ -96,7 +96,7 @@ class IrSequence(models.Model):
|
||||
|
||||
def _set_number_next_actual(self):
|
||||
for seq in self:
|
||||
seq.write({'number_next': seq.number_next_actual or 0})
|
||||
seq.write({'number_next': seq.number_next_actual or 1})
|
||||
|
||||
@api.model
|
||||
def _get_current_sequence(self):
|
||||
@@ -327,7 +327,7 @@ class IrSequenceDateRange(models.Model):
|
||||
|
||||
def _set_number_next_actual(self):
|
||||
for seq in self:
|
||||
seq.write({'number_next': seq.number_next_actual or 0})
|
||||
seq.write({'number_next': seq.number_next_actual or 1})
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
|
||||
@@ -509,7 +509,7 @@ class Partner(models.Model):
|
||||
result = True
|
||||
# To write in SUPERUSER on field is_company and avoid access rights problems.
|
||||
if 'is_company' in vals and self.user_has_groups('base.group_partner_manager') and not self.env.uid == SUPERUSER_ID:
|
||||
result = super(Partner, self).sudo().write({'is_company': vals.get('is_company')})
|
||||
result = super(Partner, self.sudo()).write({'is_company': vals.get('is_company')})
|
||||
del vals['is_company']
|
||||
result = result and super(Partner, self).write(vals)
|
||||
for partner in self:
|
||||
|
||||
@@ -282,6 +282,13 @@ class Users(models.Model):
|
||||
if any(user.company_ids and user.company_id not in user.company_ids for user in self):
|
||||
raise ValidationError(_('The chosen company is not in the allowed companies for this user'))
|
||||
|
||||
@api.multi
|
||||
@api.constrains('action_id')
|
||||
def _check_action_id(self):
|
||||
action_open_website = self.env.ref('base.action_open_website', raise_if_not_found=False)
|
||||
if action_open_website and any(user.action_id.id == action_open_website.id for user in self):
|
||||
raise ValidationError(_('The "App Switcher" action cannot be selected as home action.'))
|
||||
|
||||
@api.multi
|
||||
def read(self, fields=None, load='_classic_read'):
|
||||
if fields and self == self.env.user:
|
||||
|
||||
Reference in New Issue
Block a user