[REF] hr*: convert onchange methods to stored-editable computed fields
Impacted modules:
hr, hr_contract, hr_recruitment, hr_payroll, fleet, hr_skills, hr_appraisal, ....
Several onchanges have been converted to computed fields in the following modules :
Community :
- hr
- hr_contract
- hr_recruitment
- hr_work_entry
- hr_maintenance
- hr_expense
- hr_expense_check
- hr_holidays
- sale_expense
- account_analytic_default_hr_expense
Enterprise:
- hr_contract_salary
- hr_referral
- hr_payroll
- hr_payroll_expense
- test_l10n_be_hr_payroll_account
There are still 2 onchanges with complex behavior that couldn't be converted easily:
- an onchange that updates "tz" (timezone) that is defined as a related field
to "resource_id.tz". Apparently it is useless except to initialize the default
value of "tz".
- an onchange that updates "name" that is defined as a related field to
"resource_id.name".
the applicant.
closes odoo/odoo#45414
Taskid: 2169099
Related: odoo/enterprise#8572
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
committed by
Yannick Tivisse
parent
f50c3298bb
commit
3bd345597f
@@ -9,8 +9,6 @@ class HrExpense(models.Model):
|
||||
|
||||
@api.onchange('product_id', 'date', 'account_id')
|
||||
def _onchange_product_id(self):
|
||||
res = super(HrExpense, self)._onchange_product_id()
|
||||
rec = self.env['account.analytic.default'].sudo().account_get(product_id=self.product_id.id, account_id=self.account_id.id, company_id=self.company_id.id, date=self.date)
|
||||
self.analytic_account_id = rec.analytic_id.id
|
||||
self.analytic_tag_ids = rec.analytic_tag_ids.ids
|
||||
return res
|
||||
|
||||
@@ -192,26 +192,6 @@ class HrEmployeePrivate(models.Model):
|
||||
if employee.pin and not employee.pin.isdigit():
|
||||
raise ValidationError(_("The PIN must be a sequence of digits."))
|
||||
|
||||
@api.onchange('job_id')
|
||||
def _onchange_job_id(self):
|
||||
if self.job_id:
|
||||
self.job_title = self.job_id.name
|
||||
|
||||
@api.onchange('address_id')
|
||||
def _onchange_address(self):
|
||||
self.work_phone = self.address_id.phone
|
||||
self.mobile_phone = self.address_id.mobile
|
||||
|
||||
@api.onchange('company_id')
|
||||
def _onchange_company(self):
|
||||
address = self.company_id.partner_id.address_get(['default'])
|
||||
self.address_id = address['default'] if address else False
|
||||
|
||||
@api.onchange('department_id')
|
||||
def _onchange_department(self):
|
||||
if self.department_id.manager_id:
|
||||
self.parent_id = self.department_id.manager_id
|
||||
|
||||
@api.onchange('user_id')
|
||||
def _onchange_user(self):
|
||||
if self.user_id:
|
||||
|
||||
@@ -18,17 +18,19 @@ class HrEmployeeBase(models.AbstractModel):
|
||||
color = fields.Integer('Color Index', default=0)
|
||||
department_id = fields.Many2one('hr.department', 'Department', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
job_id = fields.Many2one('hr.job', 'Job Position', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
job_title = fields.Char("Job Title")
|
||||
job_title = fields.Char("Job Title", compute="_compute_job_title", store=True, readonly=False)
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
address_id = fields.Many2one('res.partner', 'Work Address', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
work_phone = fields.Char('Work Phone')
|
||||
mobile_phone = fields.Char('Work Mobile')
|
||||
address_id = fields.Many2one('res.partner', 'Work Address', compute="_compute_address_id", store=True, readonly=False,
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
work_phone = fields.Char('Work Phone', compute="_compute_phones", store=True, readonly=False)
|
||||
mobile_phone = fields.Char('Work Mobile', compute="_compute_phones", store=True, readonly=False)
|
||||
work_email = fields.Char('Work Email')
|
||||
work_location = fields.Char('Work Location')
|
||||
user_id = fields.Many2one('res.users')
|
||||
resource_id = fields.Many2one('resource.resource')
|
||||
resource_calendar_id = fields.Many2one('resource.calendar', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
parent_id = fields.Many2one('hr.employee', 'Manager', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
parent_id = fields.Many2one('hr.employee', 'Manager', compute="_compute_parent_id", store=True, readonly=False,
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
coach_id = fields.Many2one(
|
||||
'hr.employee', 'Coach', compute='_compute_coach', store=True, readonly=False,
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
|
||||
@@ -91,6 +93,28 @@ class HrEmployeeBase(models.AbstractModel):
|
||||
elif not employee.coach_id:
|
||||
employee.coach_id = False
|
||||
|
||||
@api.depends('job_id')
|
||||
def _compute_job_title(self):
|
||||
for employee in self.filtered('job_id'):
|
||||
employee.job_title = employee.job_id.name
|
||||
|
||||
@api.depends('address_id')
|
||||
def _compute_phones(self):
|
||||
for employee in self.filtered('address_id'):
|
||||
employee.work_phone = employee.address_id.phone
|
||||
employee.mobile_phone = employee.address_id.mobile
|
||||
|
||||
@api.depends('company_id')
|
||||
def _compute_address_id(self):
|
||||
for employee in self:
|
||||
address = employee.company_id.partner_id.address_get(['default'])
|
||||
employee.address_id = address['default'] if address else False
|
||||
|
||||
@api.depends('department_id')
|
||||
def _compute_parent_id(self):
|
||||
for employee in self.filtered('department_id.manager_id'):
|
||||
employee.parent_id = employee.department_id.manager_id
|
||||
|
||||
def _get_placeholder_filename(self, field=None):
|
||||
image_fields = ['image_%s' % size for size in [1920, 1024, 512, 256, 128]]
|
||||
if field in image_fields and not self:
|
||||
|
||||
@@ -16,7 +16,7 @@ class HrPlanActivityType(models.Model):
|
||||
domain=lambda self: ['|', ('res_model_id', '=', False), ('res_model_id', '=', self.env['ir.model']._get('hr.employee').id)],
|
||||
ondelete='restrict'
|
||||
)
|
||||
summary = fields.Char('Summary')
|
||||
summary = fields.Char('Summary', compute="_compute_default_summary", store=True, readonly=False)
|
||||
responsible = fields.Selection([
|
||||
('coach', 'Coach'),
|
||||
('manager', 'Manager'),
|
||||
@@ -25,10 +25,11 @@ class HrPlanActivityType(models.Model):
|
||||
responsible_id = fields.Many2one('res.users', 'Responsible Person', help='Specific responsible of activity if not linked to the employee.')
|
||||
note = fields.Html('Note')
|
||||
|
||||
@api.onchange('activity_type_id')
|
||||
def _onchange_activity_type_id(self):
|
||||
if self.activity_type_id and self.activity_type_id.summary and not self.summary:
|
||||
self.summary = self.activity_type_id.summary
|
||||
@api.depends('activity_type_id')
|
||||
def _compute_default_summary(self):
|
||||
for plan_type in self:
|
||||
if not plan_type.summary and plan_type.activity_type_id and plan_type.activity_type_id.summary:
|
||||
plan_type.summary = plan_type.activity_type_id.summary
|
||||
|
||||
def get_responsible_id(self, employee):
|
||||
if self.responsible == 'coach':
|
||||
|
||||
@@ -18,8 +18,10 @@ class Contract(models.Model):
|
||||
active = fields.Boolean(default=True)
|
||||
structure_type_id = fields.Many2one('hr.payroll.structure.type', string="Salary Structure Type")
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee', tracking=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
department_id = fields.Many2one('hr.department', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", string="Department")
|
||||
job_id = fields.Many2one('hr.job', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", string='Job Position')
|
||||
department_id = fields.Many2one('hr.department', compute='_compute_employee_contract', store=True, readonly=False,
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", string="Department")
|
||||
job_id = fields.Many2one('hr.job', compute='_compute_employee_contract', store=True, readonly=False,
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", string='Job Position')
|
||||
date_start = fields.Date('Start Date', required=True, default=fields.Date.today, tracking=True,
|
||||
help="Start date of the contract.")
|
||||
date_end = fields.Date('End Date', tracking=True,
|
||||
@@ -27,7 +29,7 @@ class Contract(models.Model):
|
||||
trial_date_end = fields.Date('End of Trial Period',
|
||||
help="End date of the trial period (if there is one).")
|
||||
resource_calendar_id = fields.Many2one(
|
||||
'resource.calendar', 'Working Schedule',
|
||||
'resource.calendar', 'Working Schedule', compute='_compute_employee_contract', store=True, readonly=False,
|
||||
default=lambda self: self.env.company.resource_calendar_id.id, copy=False,
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
wage = fields.Monetary('Wage', required=True, tracking=True, help="Employee's monthly gross wage.")
|
||||
@@ -39,7 +41,8 @@ class Contract(models.Model):
|
||||
('cancel', 'Cancelled')
|
||||
], string='Status', group_expand='_expand_states', copy=False,
|
||||
tracking=True, help='Status of the contract', default='draft')
|
||||
company_id = fields.Many2one('res.company', default=lambda self: self.env.company, required=True)
|
||||
company_id = fields.Many2one('res.company', compute='_compute_employee_contract', store=True, readonly=False,
|
||||
default=lambda self: self.env.company, required=True)
|
||||
company_country_id = fields.Many2one('res.country', string="Company country", related='company_id.country_id', readonly=True)
|
||||
|
||||
"""
|
||||
@@ -70,13 +73,13 @@ class Contract(models.Model):
|
||||
def _expand_states(self, states, domain, order):
|
||||
return [key for key, val in type(self).state.selection]
|
||||
|
||||
@api.onchange('employee_id')
|
||||
def _onchange_employee_id(self):
|
||||
if self.employee_id:
|
||||
self.job_id = self.employee_id.job_id
|
||||
self.department_id = self.employee_id.department_id
|
||||
self.resource_calendar_id = self.employee_id.resource_calendar_id
|
||||
self.company_id = self.employee_id.company_id
|
||||
@api.depends('employee_id')
|
||||
def _compute_employee_contract(self):
|
||||
for contract in self.filtered('employee_id'):
|
||||
contract.job_id = contract.employee_id.job_id
|
||||
contract.department_id = contract.employee_id.department_id
|
||||
contract.resource_calendar_id = contract.employee_id.resource_calendar_id
|
||||
contract.company_id = contract.employee_id.company_id
|
||||
|
||||
@api.onchange('company_id')
|
||||
def _onchange_company_id(self):
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
<field name="analytic_account_id" ref="analytic.analytic_our_super_product"/>
|
||||
<field name="product_id" ref="car_travel"/>
|
||||
<field eval="0.52" name="unit_amount"/>
|
||||
<field name="product_uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="product_uom_id" ref="uom.product_uom_km"/>
|
||||
<field name="date" eval="time.strftime('%Y')+'-01-15'"/>
|
||||
<field eval="152.0" name="quantity"/>
|
||||
<field name="sheet_id" ref="customer_meeting_sheet"/>
|
||||
|
||||
@@ -49,17 +49,26 @@ class HrExpense(models.Model):
|
||||
res = [('id', '=', employee.id), '|', ('company_id', '=', False), ('company_id', '=', employee.company_id.id)]
|
||||
return res
|
||||
|
||||
name = fields.Char('Description', readonly=True, required=True, states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]})
|
||||
name = fields.Char('Description', compute='_compute_from_product_id_company_id', store=True, required=True, copy=True,
|
||||
states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]})
|
||||
date = fields.Date(readonly=True, states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]}, default=fields.Date.context_today, string="Expense Date")
|
||||
accounting_date = fields.Date(string="Accounting Date", related='sheet_id.accounting_date', store=True, groups='account.group_account_invoice,account.group_account_readonly')
|
||||
employee_id = fields.Many2one('hr.employee', string="Employee", required=True, readonly=True, tracking=True, states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]}, default=_default_employee_id, domain=lambda self: self._get_employee_id_domain(), check_company=True)
|
||||
employee_id = fields.Many2one('hr.employee', compute='_compute_employee_id', string="Employee",
|
||||
store=True, required=True, readonly=False, tracking=True,
|
||||
states={'approved': [('readonly', True)], 'done': [('readonly', True)]},
|
||||
default=_default_employee_id, domain=lambda self: self._get_employee_id_domain(), check_company=True)
|
||||
# product_id not required to allow create an expense without product via mail alias, but should be required on the view.
|
||||
product_id = fields.Many2one('product.product', string='Product', readonly=True, tracking=True, states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]}, domain="[('can_be_expensed', '=', True), '|', ('company_id', '=', False), ('company_id', '=', company_id)]", ondelete='restrict')
|
||||
product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=_default_product_uom_id, domain="[('category_id', '=', product_uom_category_id)]")
|
||||
product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure', compute='_compute_from_product_id_company_id',
|
||||
store=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]},
|
||||
default=_default_product_uom_id, domain="[('category_id', '=', product_uom_category_id)]")
|
||||
product_uom_category_id = fields.Many2one(related='product_id.uom_id.category_id', readonly=True)
|
||||
unit_amount = fields.Float("Unit Price", readonly=True, required=True, states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]}, digits='Product Price')
|
||||
unit_amount = fields.Float("Unit Price", compute='_compute_from_product_id_company_id', store=True, required=True, copy=True,
|
||||
states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]}, digits='Product Price')
|
||||
quantity = fields.Float(required=True, readonly=True, states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]}, digits='Product Unit of Measure', default=1)
|
||||
tax_ids = fields.Many2many('account.tax', 'expense_tax', 'expense_id', 'tax_id', domain="[('company_id', '=', company_id), ('type_tax_use', '=', 'purchase')]", string='Taxes')
|
||||
tax_ids = fields.Many2many('account.tax', 'expense_tax', 'expense_id', 'tax_id',
|
||||
compute='_compute_from_product_id_company_id', store=True, readonly=False,
|
||||
domain="[('company_id', '=', company_id), ('type_tax_use', '=', 'purchase')]", string='Taxes')
|
||||
untaxed_amount = fields.Float("Subtotal", store=True, compute='_compute_amount', digits='Account')
|
||||
total_amount = fields.Monetary("Total", compute='_compute_amount', store=True, currency_field='currency_id', tracking=True)
|
||||
company_currency_id = fields.Many2one('res.currency', string="Report Company Currency", related='sheet_id.currency_id', store=True, readonly=False)
|
||||
@@ -68,7 +77,8 @@ class HrExpense(models.Model):
|
||||
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.company.currency_id)
|
||||
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', check_company=True)
|
||||
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
account_id = fields.Many2one('account.account', string='Account', default=_default_account_id, domain="[('internal_type', '=', 'other'), ('company_id', '=', company_id)]", help="An expense account is expected")
|
||||
account_id = fields.Many2one('account.account', compute='_compute_from_product_id_company_id', store=True, readonly=False, string='Account',
|
||||
default=_default_account_id, domain="[('internal_type', '=', 'other'), ('company_id', '=', company_id)]", help="An expense account is expected")
|
||||
description = fields.Text('Notes...', readonly=True, states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]})
|
||||
payment_mode = fields.Selection([
|
||||
("own_account", "Employee (to reimburse)"),
|
||||
@@ -147,24 +157,25 @@ class HrExpense(models.Model):
|
||||
else:
|
||||
expense.is_ref_editable = is_account_manager
|
||||
|
||||
@api.onchange('product_id', 'company_id')
|
||||
def _onchange_product_id(self):
|
||||
if self.product_id:
|
||||
if not self.name:
|
||||
self.name = self.product_id.display_name or ''
|
||||
self.unit_amount = self.product_id.price_compute('standard_price')[self.product_id.id]
|
||||
self.product_uom_id = self.product_id.uom_id
|
||||
self.tax_ids = self.product_id.supplier_taxes_id.filtered(lambda tax: tax.company_id == self.company_id) # taxes only from the same company
|
||||
account = self.product_id.product_tmpl_id.with_company(self.company_id)._get_product_accounts()['expense']
|
||||
@api.depends('product_id', 'company_id')
|
||||
def _compute_from_product_id_company_id(self):
|
||||
for expense in self.filtered('product_id'):
|
||||
expense = expense.with_company(expense.company_id)
|
||||
expense.name = expense.name or expense.product_id.display_name
|
||||
expense.unit_amount = expense.product_id.price_compute('standard_price')[expense.product_id.id]
|
||||
expense.product_uom_id = expense.product_id.uom_id
|
||||
expense.tax_ids = expense.product_id.supplier_taxes_id.filtered(lambda tax: tax.company_id == expense.company_id) # taxes only from the same company
|
||||
account = expense.product_id.product_tmpl_id._get_product_accounts()['expense']
|
||||
if account:
|
||||
self.account_id = account
|
||||
expense.account_id = account
|
||||
|
||||
@api.onchange('company_id')
|
||||
def _onchange_expense_company_id(self):
|
||||
self.employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid), ('company_id', '=', self.company_id.id)])
|
||||
@api.depends('company_id')
|
||||
def _compute_employee_id(self):
|
||||
for expense in self:
|
||||
expense.employee_id = self.env.user.with_company(expense.company_id).employee_id
|
||||
|
||||
@api.onchange('product_uom_id')
|
||||
def _onchange_product_uom_id(self):
|
||||
@api.constrains('product_id', 'product_uom_id')
|
||||
def _check_product_uom_category(self):
|
||||
if self.product_id and self.product_uom_id.category_id != self.product_id.uom_id.category_id:
|
||||
raise UserError(_('Selected Unit of Measure does not belong to the same category as the product Unit of Measure.'))
|
||||
|
||||
@@ -727,9 +738,9 @@ class HrExpenseSheet(models.Model):
|
||||
('cancel', 'Refused')
|
||||
], string='Status', index=True, readonly=True, tracking=True, copy=False, default='draft', required=True, help='Expense Report State')
|
||||
employee_id = fields.Many2one('hr.employee', string="Employee", required=True, readonly=True, tracking=True, states={'draft': [('readonly', False)]}, default=_default_employee_id, check_company=True)
|
||||
address_id = fields.Many2one('res.partner', string="Employee Home Address", check_company=True)
|
||||
address_id = fields.Many2one('res.partner', compute='_compute_from_employee_id', store=True, readonly=False, copy=True, string="Employee Home Address", check_company=True)
|
||||
payment_mode = fields.Selection(related='expense_line_ids.payment_mode', default='own_account', readonly=True, string="Paid By", tracking=True)
|
||||
user_id = fields.Many2one('res.users', 'Manager', readonly=True, copy=False, states={'draft': [('readonly', False)]}, tracking=True)
|
||||
user_id = fields.Many2one('res.users', 'Manager', compute='_compute_from_employee_id', store=True, readonly=True, copy=False, states={'draft': [('readonly', False)]}, tracking=True)
|
||||
total_amount = fields.Monetary('Total Amount', currency_field='currency_id', compute='_compute_amount', store=True, tracking=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.company)
|
||||
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.company.currency_id)
|
||||
@@ -740,7 +751,7 @@ class HrExpenseSheet(models.Model):
|
||||
default=_default_bank_journal_id, help="The payment method used when the expense is paid by the company.")
|
||||
accounting_date = fields.Date("Accounting Date")
|
||||
account_move_id = fields.Many2one('account.move', string='Journal Entry', ondelete='restrict', copy=False, readonly=True)
|
||||
department_id = fields.Many2one('hr.department', string='Department', states={'post': [('readonly', True)], 'done': [('readonly', True)]})
|
||||
department_id = fields.Many2one('hr.department', compute='_compute_from_employee_id', store=True, readonly=False, copy=False, string='Department', states={'post': [('readonly', True)], 'done': [('readonly', True)]})
|
||||
is_multiple_currency = fields.Boolean("Handle lines with different currencies", compute='_compute_is_multiple_currency')
|
||||
can_reset = fields.Boolean('Can Reset', compute='_compute_can_reset')
|
||||
|
||||
@@ -767,11 +778,12 @@ class HrExpenseSheet(models.Model):
|
||||
for sheet in self:
|
||||
sheet.can_reset = is_expense_user if is_expense_user else sheet.employee_id.user_id == self.env.user
|
||||
|
||||
@api.onchange('employee_id')
|
||||
def _onchange_employee_id(self):
|
||||
self.address_id = self.employee_id.sudo().address_home_id
|
||||
self.department_id = self.employee_id.department_id
|
||||
self.user_id = self.employee_id.expense_manager_id or self.employee_id.parent_id.user_id
|
||||
@api.depends('employee_id')
|
||||
def _compute_from_employee_id(self):
|
||||
for sheet in self:
|
||||
sheet.address_id = sheet.employee_id.sudo().address_home_id
|
||||
sheet.department_id = sheet.employee_id.department_id
|
||||
sheet.user_id = sheet.employee_id.expense_manager_id or sheet.employee_id.parent_id.user_id
|
||||
|
||||
@api.constrains('expense_line_ids')
|
||||
def _check_payment_mode(self):
|
||||
|
||||
@@ -7,7 +7,8 @@ from odoo import api, fields, models
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = "product.template"
|
||||
|
||||
can_be_expensed = fields.Boolean(string="Can be Expensed", help="Specify whether the product can be selected in an expense.")
|
||||
can_be_expensed = fields.Boolean(string="Can be Expensed", compute='_compute_can_be_expensed',
|
||||
store=True, readonly=False, help="Specify whether the product can be selected in an expense.")
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
@@ -18,7 +19,6 @@ class ProductTemplate(models.Model):
|
||||
vals.update({'supplier_taxes_id': False})
|
||||
return super(ProductTemplate, self).create(vals_list)
|
||||
|
||||
@api.onchange('type')
|
||||
def _onchange_type_for_expense(self):
|
||||
if self.type not in ['consu', 'service']: # storable can not be expensed.
|
||||
self.can_be_expensed = False
|
||||
@api.depends('type')
|
||||
def _compute_can_be_expensed(self):
|
||||
self.filtered(lambda p: p.type not in ['consu', 'service']).update({'can_be_expensed': False})
|
||||
|
||||
@@ -6,7 +6,8 @@ from odoo import api, fields, models
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
expense_alias_prefix = fields.Char('Default Alias Name for Expenses')
|
||||
expense_alias_prefix = fields.Char('Default Alias Name for Expenses', compute='_compute_expense_alias_prefix',
|
||||
store=True, readonly=False)
|
||||
use_mailgateway = fields.Boolean(string='Let your employees record expenses by email',
|
||||
config_parameter='hr_expense.use_mailgateway')
|
||||
|
||||
@@ -26,7 +27,6 @@ class ResConfigSettings(models.TransientModel):
|
||||
super(ResConfigSettings, self).set_values()
|
||||
self.env.ref('hr_expense.mail_alias_expense').write({'alias_name': self.expense_alias_prefix})
|
||||
|
||||
@api.onchange('use_mailgateway')
|
||||
def _onchange_use_mailgateway(self):
|
||||
if not self.use_mailgateway:
|
||||
self.expense_alias_prefix = False
|
||||
@api.depends('use_mailgateway')
|
||||
def _compute_expense_alias_prefix(self):
|
||||
self.filtered(lambda w: not w.use_mailgateway).update({'expense_alias_prefix': False})
|
||||
|
||||
@@ -42,7 +42,6 @@ class TestAccountEntry(TestExpenseCommon):
|
||||
'sheet_id': expense.id,
|
||||
'analytic_account_id': self.analytic_account.id,
|
||||
})
|
||||
expense_line._onchange_product_id()
|
||||
# State should default to draft
|
||||
self.assertEqual(expense.state, 'draft', 'Expense should be created in Draft state')
|
||||
# Submitted to Manager
|
||||
@@ -106,7 +105,6 @@ class TestAccountEntry(TestExpenseCommon):
|
||||
'analytic_account_id': self.analytic_account.id,
|
||||
'currency_id': self.env.ref('base.EUR').id,
|
||||
})
|
||||
expense_line._onchange_product_id()
|
||||
# State should default to draft
|
||||
self.assertEqual(expense.state, 'draft', 'Expense should be created in Draft state')
|
||||
# Submitted to Manager
|
||||
|
||||
@@ -29,13 +29,12 @@ class HrExpenseSheetRegisterPaymentWizard(models.TransientModel):
|
||||
|
||||
expense_sheet_id = fields.Many2one('hr.expense.sheet', string="Expense Report", required=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Partner', required=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
partner_bank_account_id = fields.Many2one('res.partner.bank', string="Recipient Bank Account", domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
partner_bank_account_id = fields.Many2one('res.partner.bank', compute='_compute_partner_bank_account_id', store=True, readonly=False,
|
||||
string="Recipient Bank Account", domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
journal_id = fields.Many2one('account.journal', string='Payment Method', required=True, domain="[('type', 'in', ('bank', 'cash')), ('company_id', '=', company_id)]")
|
||||
company_id = fields.Many2one('res.company', related='expense_sheet_id.company_id', string='Company', readonly=True)
|
||||
payment_method_id = fields.Many2one('account.payment.method', string='Payment Type', required=True, domain="""[
|
||||
('payment_type', '=', 'outbound'),
|
||||
('id', 'in', available_payment_methods),
|
||||
]""")
|
||||
payment_method_id = fields.Many2one('account.payment.method', compute='_compute_payment_method_id', store=True, readonly=False,
|
||||
string='Payment Type', required=True, domain="[('payment_type', '=', 'outbound'), ('id', 'in', available_payment_methods)]")
|
||||
available_payment_methods = fields.Many2many(related='journal_id.outbound_payment_method_ids')
|
||||
amount = fields.Monetary(string='Payment Amount', required=True)
|
||||
currency_id = fields.Many2one('res.currency', string='Currency', required=True, default=lambda self: self.env.company.currency_id)
|
||||
@@ -46,15 +45,14 @@ class HrExpenseSheetRegisterPaymentWizard(models.TransientModel):
|
||||
show_partner_bank_account = fields.Boolean(compute='_compute_show_partner_bank', help='Technical field used to know whether the field `partner_bank_account_id` needs to be displayed or not in the payments form views')
|
||||
require_partner_bank_account = fields.Boolean(compute='_compute_show_partner_bank', help='Technical field used to know whether the field `partner_bank_account_id` needs to be required or not in the payments form views')
|
||||
|
||||
@api.onchange('partner_id')
|
||||
def _onchange_partner_id(self):
|
||||
expense_sheet = self.expense_sheet_id
|
||||
if expense_sheet.employee_id.id and expense_sheet.employee_id.sudo().bank_account_id.id:
|
||||
self.partner_bank_account_id = expense_sheet.employee_id.sudo().bank_account_id.id
|
||||
elif self.partner_id and len(self.partner_id.bank_ids) > 0:
|
||||
self.partner_bank_account_id = self.partner_id.bank_ids[0]
|
||||
else:
|
||||
self.partner_bank_account_id = False
|
||||
@api.depends('partner_id')
|
||||
def _compute_partner_bank_account_id(self):
|
||||
for wizard in self:
|
||||
expense_sheet = wizard.expense_sheet_id
|
||||
if expense_sheet.employee_id.id and expense_sheet.employee_id.sudo().bank_account_id.id:
|
||||
wizard.partner_bank_account_id = expense_sheet.employee_id.sudo().bank_account_id.id
|
||||
else:
|
||||
wizard.partner_bank_account_id = wizard.partner_id.bank_ids[:1]
|
||||
|
||||
@api.constrains('amount')
|
||||
def _check_amount(self):
|
||||
@@ -80,11 +78,11 @@ class HrExpenseSheetRegisterPaymentWizard(models.TransientModel):
|
||||
wizard.hide_payment_method = (len(journal_payment_methods) == 1
|
||||
and journal_payment_methods[0].code == 'manual')
|
||||
|
||||
@api.onchange('journal_id')
|
||||
def _onchange_journal(self):
|
||||
if self.journal_id:
|
||||
@api.depends('journal_id')
|
||||
def _compute_payment_method_id(self):
|
||||
for wizard in self.filtered('journal_id'):
|
||||
# Set default payment method (we consider the first to be the default one)
|
||||
self.payment_method_id = self.journal_id.outbound_payment_method_ids[:1]
|
||||
wizard.payment_method_id = wizard.journal_id.outbound_payment_method_ids[:1]
|
||||
|
||||
def _get_payment_vals(self):
|
||||
""" Hook for extension """
|
||||
|
||||
@@ -6,10 +6,10 @@ from odoo import models, fields, api
|
||||
class HrExpenseRegisterPaymentWizard(models.TransientModel):
|
||||
_inherit = "hr.expense.sheet.register.payment.wizard"
|
||||
|
||||
check_amount_in_words = fields.Char(string="Amount in Words")
|
||||
check_amount_in_words = fields.Char(string="Amount in Words", compute='_compute_check_amount_in_words', store=True, readonly=False)
|
||||
check_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing', readonly=False)
|
||||
# Note: a check_number == 0 means that it will be attributed when the check is printed
|
||||
check_number = fields.Char(string="Check Number", readonly=True, copy=False, default=0,
|
||||
check_number = fields.Char(string="Check Number", compute='_compute_check_number', store=True, copy=False,
|
||||
help="Number of the check corresponding to this payment. If your pre-printed check are not already numbered, "
|
||||
"you can manage the numbering in the journal configuration page.")
|
||||
payment_method_code_2 = fields.Char(related='payment_method_id.code',
|
||||
@@ -17,18 +17,18 @@ class HrExpenseRegisterPaymentWizard(models.TransientModel):
|
||||
string="Payment Method Code 2",
|
||||
readonly=True)
|
||||
|
||||
@api.onchange('journal_id')
|
||||
def _onchange_journal_id(self):
|
||||
if hasattr(super(HrExpenseRegisterPaymentWizard, self), '_onchange_journal_id'):
|
||||
super(HrExpenseRegisterPaymentWizard, self)._onchange_journal_id()
|
||||
if self.journal_id.check_manual_sequencing:
|
||||
self.check_number = self.journal_id.check_sequence_id.number_next_actual
|
||||
@api.depends('journal_id')
|
||||
def _compute_check_number(self):
|
||||
for wizard in self:
|
||||
if wizard.journal_id.check_manual_sequencing:
|
||||
wizard.check_number = wizard.journal_id.check_sequence_id.number_next_actual
|
||||
else:
|
||||
wizard.check_number = 0
|
||||
|
||||
@api.onchange('amount')
|
||||
def _onchange_amount(self):
|
||||
if hasattr(super(HrExpenseRegisterPaymentWizard, self), '_onchange_amount'):
|
||||
super(HrExpenseRegisterPaymentWizard, self)._onchange_amount()
|
||||
self.check_amount_in_words = self.currency_id.amount_to_text(self.amount)
|
||||
@api.depends('amount')
|
||||
def _compute_check_amount_in_words(self):
|
||||
for wizard in self:
|
||||
wizard.check_amount_in_words = wizard.currency_id.amount_to_text(wizard.amount)
|
||||
|
||||
def _get_payment_vals(self):
|
||||
res = super(HrExpenseRegisterPaymentWizard, self)._get_payment_vals()
|
||||
|
||||
@@ -24,7 +24,7 @@ from odoo.osv import expression
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
# Used to agglomerate the attendances in order to find the hour_from and hour_to
|
||||
# See _onchange_request_parameters
|
||||
# See _compute_date_from_to
|
||||
DummyAttendance = namedtuple('DummyAttendance', 'hour_from, hour_to, dayofweek, day_period, week_type')
|
||||
|
||||
class HolidaysRequest(models.Model):
|
||||
@@ -80,9 +80,6 @@ class HolidaysRequest(models.Model):
|
||||
defaults['state'] = 'confirm' if lt and lt.leave_validation_type != 'no_validation' else 'draft'
|
||||
return defaults
|
||||
|
||||
def _default_employee(self):
|
||||
return self.env.context.get('default_employee_id') or self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
|
||||
|
||||
def _default_get_request_parameters(self, values):
|
||||
new_values = dict(values)
|
||||
global_from, global_to = False, False
|
||||
@@ -112,7 +109,7 @@ class HolidaysRequest(models.Model):
|
||||
('refuse', 'Refused'),
|
||||
('validate1', 'Second Approval'),
|
||||
('validate', 'Approved')
|
||||
], string='Status', readonly=True, tracking=True, copy=False, default='draft',
|
||||
], string='Status', compute='_compute_state', store=True, tracking=True, copy=False,
|
||||
help="The status is set to 'To Submit', when a time off request is created." +
|
||||
"\nThe status is 'To Approve', when time off request is confirmed by user." +
|
||||
"\nThe status is 'Refused', when time off request is refused by manager." +
|
||||
@@ -120,35 +117,36 @@ class HolidaysRequest(models.Model):
|
||||
payslip_status = fields.Boolean('Reported in last payslips', help='Green this button when the time off has been taken into account in the payslip.', copy=False)
|
||||
report_note = fields.Text('HR Comments', copy=False, groups="hr_holidays.group_hr_holidays_manager")
|
||||
user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', related_sudo=True, compute_sudo=True, store=True, default=lambda self: self.env.uid, readonly=True)
|
||||
manager_id = fields.Many2one('hr.employee')
|
||||
manager_id = fields.Many2one('hr.employee', compute='_compute_from_employee_id', store=True, readonly=False)
|
||||
# leave type configuration
|
||||
holiday_status_id = fields.Many2one(
|
||||
"hr.leave.type", string="Time Off Type", required=True, readonly=True,
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
|
||||
"hr.leave.type", compute='_compute_from_employee_id', store=True, string="Time Off Type", required=True, readonly=False,
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]},
|
||||
domain=[('valid', '=', True)])
|
||||
validation_type = fields.Selection(string='Validation Type', related='holiday_status_id.leave_validation_type', readonly=False)
|
||||
# HR data
|
||||
|
||||
employee_id = fields.Many2one(
|
||||
'hr.employee', string='Employee', index=True, readonly=True, ondelete="restrict",
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=_default_employee, tracking=True)
|
||||
'hr.employee', compute='_compute_from_holiday_type', store=True, string='Employee', index=True, readonly=False, ondelete="restrict",
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]},
|
||||
tracking=True)
|
||||
tz_mismatch = fields.Boolean(compute='_compute_tz_mismatch')
|
||||
tz = fields.Selection(_tz_get, compute='_compute_tz')
|
||||
department_id = fields.Many2one(
|
||||
'hr.department', string='Department', readonly=True,
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
|
||||
'hr.department', compute='_compute_department_id', store=True, string='Department', readonly=False,
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
|
||||
notes = fields.Text('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
|
||||
# duration
|
||||
date_from = fields.Datetime(
|
||||
'Start Date', readonly=True, index=True, copy=False, required=True,
|
||||
'Start Date', compute='_compute_date_from_to', store=True, readonly=False, index=True, copy=False, required=True, tracking=True,
|
||||
default=fields.Datetime.now,
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, tracking=True)
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
|
||||
date_to = fields.Datetime(
|
||||
'End Date', readonly=True, copy=False, required=True,
|
||||
'End Date', compute='_compute_date_from_to', store=True, readonly=False, copy=False, required=True, tracking=True,
|
||||
default=fields.Datetime.now,
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, tracking=True)
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
|
||||
number_of_days = fields.Float(
|
||||
'Duration (Days)', copy=False, tracking=True,
|
||||
'Duration (Days)', compute='_compute_number_of_days', store=True, readonly=False, copy=False, tracking=True,
|
||||
help='Number of days of the time off request. Used in the calculation. To manually correct the duration, use this field.')
|
||||
number_of_days_display = fields.Float(
|
||||
'Duration in days', compute='_compute_number_of_days_display', readonly=True,
|
||||
@@ -171,10 +169,10 @@ class HolidaysRequest(models.Model):
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
|
||||
help='By Employee: Allocation/Request for individual Employee, By Employee Tag: Allocation/Request for group of employees in category')
|
||||
category_id = fields.Many2one(
|
||||
'hr.employee.category', string='Employee Tag', readonly=True,
|
||||
'hr.employee.category', compute='_compute_from_holiday_type', store=True, string='Employee Tag',
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, help='Category of Employee')
|
||||
mode_company_id = fields.Many2one(
|
||||
'res.company', string='Company Mode', readonly=True,
|
||||
'res.company', compute='_compute_from_holiday_type', store=True, string='Company Mode',
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
|
||||
first_approver_id = fields.Many2one(
|
||||
'hr.employee', string='First Approval', readonly=True, copy=False,
|
||||
@@ -246,9 +244,9 @@ class HolidaysRequest(models.Model):
|
||||
('am', 'Morning'), ('pm', 'Afternoon')],
|
||||
string="Date Period Start", default='am')
|
||||
# request type
|
||||
request_unit_half = fields.Boolean('Half Day')
|
||||
request_unit_hours = fields.Boolean('Custom Hours')
|
||||
request_unit_custom = fields.Boolean('Days-long custom hours')
|
||||
request_unit_half = fields.Boolean('Half Day', compute='_compute_request_unit_half', store=True, readonly=False)
|
||||
request_unit_hours = fields.Boolean('Custom Hours', compute='_compute_request_unit_hours', store=True, readonly=False)
|
||||
request_unit_custom = fields.Boolean('Days-long custom hours', compute='_compute_request_unit_custom', store=True, readonly=False)
|
||||
|
||||
_sql_constraints = [
|
||||
('type_value',
|
||||
@@ -297,158 +295,153 @@ class HolidaysRequest(models.Model):
|
||||
leaves = self.search(domain)
|
||||
return [('id', 'in', leaves.ids)]
|
||||
|
||||
@api.onchange('holiday_status_id')
|
||||
def _onchange_holiday_status_id(self):
|
||||
self.request_unit_half = False
|
||||
self.request_unit_hours = False
|
||||
self.request_unit_custom = False
|
||||
self.state = 'confirm' if self.validation_type != 'no_validation' else 'draft'
|
||||
@api.depends('holiday_status_id')
|
||||
def _compute_state(self):
|
||||
for holiday in self:
|
||||
holiday.state = 'confirm' if holiday.validation_type != 'no_validation' else 'draft'
|
||||
|
||||
@api.onchange('request_date_from_period', 'request_hour_from', 'request_hour_to',
|
||||
'request_date_from', 'request_date_to',
|
||||
'employee_id')
|
||||
def _onchange_request_parameters(self):
|
||||
if not self.request_date_from:
|
||||
self.date_from = False
|
||||
return
|
||||
|
||||
if self.request_unit_half or self.request_unit_hours:
|
||||
self.request_date_to = self.request_date_from
|
||||
|
||||
if not self.request_date_to:
|
||||
self.date_to = False
|
||||
return
|
||||
|
||||
if self.request_date_from > self.request_date_to:
|
||||
self.request_date_to = self.request_date_from
|
||||
|
||||
resource_calendar_id = self.employee_id.resource_calendar_id or self.env.company.resource_calendar_id
|
||||
domain = [('calendar_id', '=', resource_calendar_id.id), ('display_type', '=', False)]
|
||||
attendances = self.env['resource.calendar.attendance'].read_group(domain, ['ids:array_agg(id)', 'hour_from:min(hour_from)', 'hour_to:max(hour_to)', 'week_type', 'dayofweek', 'day_period'], ['week_type', 'dayofweek', 'day_period'], lazy=False)
|
||||
|
||||
# Must be sorted by dayofweek ASC and day_period DESC
|
||||
attendances = sorted([DummyAttendance(group['hour_from'], group['hour_to'], group['dayofweek'], group['day_period'], group['week_type']) for group in attendances], key=lambda att: (att.dayofweek, att.day_period != 'morning'))
|
||||
|
||||
default_value = DummyAttendance(0, 0, 0, 'morning', False)
|
||||
|
||||
if resource_calendar_id.two_weeks_calendar:
|
||||
# find week type of start_date
|
||||
start_week_type = int(math.floor((self.request_date_from.toordinal() - 1) / 7) % 2)
|
||||
attendance_actual_week = [att for att in attendances if att.week_type is False or int(att.week_type) == start_week_type]
|
||||
attendance_actual_next_week = [att for att in attendances if att.week_type is False or int(att.week_type) != start_week_type]
|
||||
# First, add days of actual week coming after date_from
|
||||
attendance_filtred = [att for att in attendance_actual_week if int(att.dayofweek) >= self.request_date_from.weekday()]
|
||||
# Second, add days of the other type of week
|
||||
attendance_filtred += list(attendance_actual_next_week)
|
||||
# Third, add days of actual week (to consider days that we have remove first because they coming before date_from)
|
||||
attendance_filtred += list(attendance_actual_week)
|
||||
|
||||
end_week_type = int(math.floor((self.request_date_to.toordinal() - 1) / 7) % 2)
|
||||
attendance_actual_week = [att for att in attendances if att.week_type is False or int(att.week_type) == end_week_type]
|
||||
attendance_actual_next_week = [att for att in attendances if att.week_type is False or int(att.week_type) != end_week_type]
|
||||
attendance_filtred_reversed = list(reversed([att for att in attendance_actual_week if int(att.dayofweek) <= self.request_date_to.weekday()]))
|
||||
attendance_filtred_reversed += list(reversed(attendance_actual_next_week))
|
||||
attendance_filtred_reversed += list(reversed(attendance_actual_week))
|
||||
|
||||
# find first attendance coming after first_day
|
||||
attendance_from = attendance_filtred[0]
|
||||
# find last attendance coming before last_day
|
||||
attendance_to = attendance_filtred_reversed[0]
|
||||
else:
|
||||
# find first attendance coming after first_day
|
||||
attendance_from = next((att for att in attendances if int(att.dayofweek) >= self.request_date_from.weekday()), attendances[0] if attendances else default_value)
|
||||
# find last attendance coming before last_day
|
||||
attendance_to = next((att for att in reversed(attendances) if int(att.dayofweek) <= self.request_date_to.weekday()), attendances[-1] if attendances else default_value)
|
||||
|
||||
compensated_request_date_from = self.request_date_from
|
||||
compensated_request_date_to = self.request_date_to
|
||||
|
||||
if self.request_unit_half:
|
||||
if self.request_date_from_period == 'am':
|
||||
hour_from = float_to_time(attendance_from.hour_from)
|
||||
hour_to = float_to_time(attendance_from.hour_to)
|
||||
@api.depends('request_date_from_period', 'request_hour_from', 'request_hour_to', 'request_date_from', 'request_date_to',
|
||||
'request_unit_half', 'request_unit_hours', 'request_unit_custom', 'employee_id')
|
||||
def _compute_date_from_to(self):
|
||||
for holiday in self:
|
||||
if holiday.request_date_from > holiday.request_date_to:
|
||||
holiday.request_date_to = holiday.request_date_from
|
||||
if not holiday.request_date_from:
|
||||
holiday.date_from = False
|
||||
elif not holiday.request_unit_half and not holiday.request_unit_hours and not holiday.request_date_to:
|
||||
holiday.date_to = False
|
||||
else:
|
||||
hour_from = float_to_time(attendance_to.hour_from)
|
||||
hour_to = float_to_time(attendance_to.hour_to)
|
||||
elif self.request_unit_hours:
|
||||
hour_from = float_to_time(float(self.request_hour_from))
|
||||
hour_to = float_to_time(float(self.request_hour_to))
|
||||
elif self.request_unit_custom:
|
||||
hour_from = self.date_from.time()
|
||||
hour_to = self.date_to.time()
|
||||
compensated_request_date_from = self._adjust_date_based_on_tz(self.request_date_from, hour_from)
|
||||
compensated_request_date_to = self._adjust_date_based_on_tz(self.request_date_to, hour_to)
|
||||
else:
|
||||
hour_from = float_to_time(attendance_from.hour_from)
|
||||
hour_to = float_to_time(attendance_to.hour_to)
|
||||
if holiday.request_unit_half or holiday.request_unit_hours:
|
||||
holiday.request_date_to = holiday.request_date_from
|
||||
resource_calendar_id = holiday.employee_id.resource_calendar_id or self.env.company.resource_calendar_id
|
||||
domain = [('calendar_id', '=', resource_calendar_id.id), ('display_type', '=', False)]
|
||||
attendances = self.env['resource.calendar.attendance'].read_group(domain, ['ids:array_agg(id)', 'hour_from:min(hour_from)', 'hour_to:max(hour_to)', 'week_type', 'dayofweek', 'day_period'], ['week_type', 'dayofweek', 'day_period'], lazy=False)
|
||||
|
||||
self.date_from = timezone(self.tz).localize(datetime.combine(compensated_request_date_from, hour_from)).astimezone(UTC).replace(tzinfo=None)
|
||||
self.date_to = timezone(self.tz).localize(datetime.combine(compensated_request_date_to, hour_to)).astimezone(UTC).replace(tzinfo=None)
|
||||
# Must be sorted by dayofweek ASC and day_period DESC
|
||||
attendances = sorted([DummyAttendance(group['hour_from'], group['hour_to'], group['dayofweek'], group['day_period'], group['week_type']) for group in attendances], key=lambda att: (att.dayofweek, att.day_period != 'morning'))
|
||||
|
||||
self._onchange_leave_dates()
|
||||
default_value = DummyAttendance(0, 0, 0, 'morning', False)
|
||||
|
||||
@api.onchange('request_unit_half')
|
||||
def _onchange_request_unit_half(self):
|
||||
if self.request_unit_half:
|
||||
self.request_unit_hours = False
|
||||
self.request_unit_custom = False
|
||||
self._onchange_request_parameters()
|
||||
if resource_calendar_id.two_weeks_calendar:
|
||||
# find week type of start_date
|
||||
start_week_type = int(math.floor((holiday.request_date_from.toordinal() - 1) / 7) % 2)
|
||||
attendance_actual_week = [att for att in attendances if att.week_type is False or int(att.week_type) == start_week_type]
|
||||
attendance_actual_next_week = [att for att in attendances if att.week_type is False or int(att.week_type) != start_week_type]
|
||||
# First, add days of actual week coming after date_from
|
||||
attendance_filtred = [att for att in attendance_actual_week if int(att.dayofweek) >= holiday.request_date_from.weekday()]
|
||||
# Second, add days of the other type of week
|
||||
attendance_filtred += list(attendance_actual_next_week)
|
||||
# Third, add days of actual week (to consider days that we have remove first because they coming before date_from)
|
||||
attendance_filtred += list(attendance_actual_week)
|
||||
|
||||
@api.onchange('request_unit_hours')
|
||||
def _onchange_request_unit_hours(self):
|
||||
if self.request_unit_hours:
|
||||
self.request_unit_half = False
|
||||
self.request_unit_custom = False
|
||||
self._onchange_request_parameters()
|
||||
end_week_type = int(math.floor((holiday.request_date_to.toordinal() - 1) / 7) % 2)
|
||||
attendance_actual_week = [att for att in attendances if att.week_type is False or int(att.week_type) == end_week_type]
|
||||
attendance_actual_next_week = [att for att in attendances if att.week_type is False or int(att.week_type) != end_week_type]
|
||||
attendance_filtred_reversed = list(reversed([att for att in attendance_actual_week if int(att.dayofweek) <= holiday.request_date_to.weekday()]))
|
||||
attendance_filtred_reversed += list(reversed(attendance_actual_next_week))
|
||||
attendance_filtred_reversed += list(reversed(attendance_actual_week))
|
||||
|
||||
@api.onchange('request_unit_custom')
|
||||
def _onchange_request_unit_custom(self):
|
||||
if self.request_unit_custom:
|
||||
self.request_unit_half = False
|
||||
self.request_unit_hours = False
|
||||
self._onchange_request_parameters()
|
||||
# find first attendance coming after first_day
|
||||
attendance_from = attendance_filtred[0]
|
||||
# find last attendance coming before last_day
|
||||
attendance_to = attendance_filtred_reversed[0]
|
||||
else:
|
||||
# find first attendance coming after first_day
|
||||
attendance_from = next((att for att in attendances if int(att.dayofweek) >= holiday.request_date_from.weekday()), attendances[0] if attendances else default_value)
|
||||
# find last attendance coming before last_day
|
||||
attendance_to = next((att for att in reversed(attendances) if int(att.dayofweek) <= holiday.request_date_to.weekday()), attendances[-1] if attendances else default_value)
|
||||
|
||||
@api.onchange('holiday_type')
|
||||
def _onchange_type(self):
|
||||
if self.holiday_type == 'employee':
|
||||
if not self.employee_id:
|
||||
self.employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1).id
|
||||
self.mode_company_id = False
|
||||
self.category_id = False
|
||||
elif self.holiday_type == 'company':
|
||||
self.employee_id = False
|
||||
if not self.mode_company_id:
|
||||
self.mode_company_id = self.env.company.id
|
||||
self.category_id = False
|
||||
elif self.holiday_type == 'department':
|
||||
self.employee_id = False
|
||||
self.mode_company_id = False
|
||||
self.category_id = False
|
||||
if not self.department_id:
|
||||
self.department_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1).department_id.id
|
||||
elif self.holiday_type == 'category':
|
||||
self.employee_id = False
|
||||
self.mode_company_id = False
|
||||
self.department_id = False
|
||||
compensated_request_date_from = holiday.request_date_from
|
||||
compensated_request_date_to = holiday.request_date_to
|
||||
|
||||
def _sync_employee_details(self):
|
||||
if holiday.request_unit_half:
|
||||
if holiday.request_date_from_period == 'am':
|
||||
hour_from = float_to_time(attendance_from.hour_from)
|
||||
hour_to = float_to_time(attendance_from.hour_to)
|
||||
else:
|
||||
hour_from = float_to_time(attendance_to.hour_from)
|
||||
hour_to = float_to_time(attendance_to.hour_to)
|
||||
elif holiday.request_unit_hours:
|
||||
hour_from = float_to_time(float(holiday.request_hour_from))
|
||||
hour_to = float_to_time(float(holiday.request_hour_to))
|
||||
elif holiday.request_unit_custom:
|
||||
hour_from = holiday.date_from.time()
|
||||
hour_to = holiday.date_to.time()
|
||||
compensated_request_date_from = holiday._adjust_date_based_on_tz(holiday.request_date_from, hour_from)
|
||||
compensated_request_date_to = holiday._adjust_date_based_on_tz(holiday.request_date_to, hour_to)
|
||||
else:
|
||||
hour_from = float_to_time(attendance_from.hour_from)
|
||||
hour_to = float_to_time(attendance_to.hour_to)
|
||||
|
||||
holiday.date_from = timezone(holiday.tz).localize(datetime.combine(compensated_request_date_from, hour_from)).astimezone(UTC).replace(tzinfo=None)
|
||||
holiday.date_to = timezone(holiday.tz).localize(datetime.combine(compensated_request_date_to, hour_to)).astimezone(UTC).replace(tzinfo=None)
|
||||
|
||||
@api.depends('holiday_status_id', 'request_unit_hours', 'request_unit_custom')
|
||||
def _compute_request_unit_half(self):
|
||||
for holiday in self:
|
||||
if holiday.holiday_status_id or holiday.request_unit_hours or holiday.request_unit_custom:
|
||||
holiday.request_unit_half = False
|
||||
|
||||
@api.depends('holiday_status_id', 'request_unit_half', 'request_unit_custom')
|
||||
def _compute_request_unit_hours(self):
|
||||
for holiday in self:
|
||||
if holiday.holiday_status_id or holiday.request_unit_half or holiday.request_unit_custom:
|
||||
holiday.request_unit_hours = False
|
||||
|
||||
@api.depends('holiday_status_id', 'request_unit_half', 'request_unit_hours')
|
||||
def _compute_request_unit_custom(self):
|
||||
for holiday in self:
|
||||
if holiday.holiday_status_id or holiday.request_unit_half or holiday.request_unit_hours:
|
||||
holiday.request_unit_custom = False
|
||||
|
||||
@api.depends('holiday_type')
|
||||
def _compute_from_holiday_type(self):
|
||||
for holiday in self:
|
||||
if holiday.holiday_type == 'employee':
|
||||
if not holiday.employee_id:
|
||||
holiday.employee_id = self.env.user.employee_id
|
||||
holiday.mode_company_id = False
|
||||
holiday.category_id = False
|
||||
elif holiday.holiday_type == 'company':
|
||||
holiday.employee_id = False
|
||||
if not holiday.mode_company_id:
|
||||
holiday.mode_company_id = self.env.company.id
|
||||
holiday.category_id = False
|
||||
elif holiday.holiday_type == 'department':
|
||||
holiday.employee_id = False
|
||||
holiday.mode_company_id = False
|
||||
holiday.category_id = False
|
||||
elif holiday.holiday_type == 'category':
|
||||
holiday.employee_id = False
|
||||
holiday.mode_company_id = False
|
||||
else:
|
||||
holiday.employee_id = self.env.context.get('default_employee_id') or self.env.user.employee_id
|
||||
|
||||
@api.depends('employee_id')
|
||||
def _compute_from_employee_id(self):
|
||||
for holiday in self:
|
||||
holiday.manager_id = holiday.employee_id.parent_id.id
|
||||
if holiday.employee_id.user_id != self.env.user and self._origin.employee_id != holiday.employee_id:
|
||||
holiday.holiday_status_id = False
|
||||
|
||||
@api.depends('employee_id', 'holiday_type')
|
||||
def _compute_department_id(self):
|
||||
for holiday in self:
|
||||
if holiday.employee_id:
|
||||
holiday.department_id = holiday.employee_id.department_id
|
||||
elif holiday.holiday_type == 'department':
|
||||
if not holiday.department_id:
|
||||
holiday.department_id = self.env.user.employee_id.department_id
|
||||
else:
|
||||
holiday.department_id = False
|
||||
|
||||
@api.onchange('employee_id')
|
||||
def _onchange_employee_id(self):
|
||||
self._sync_employee_details()
|
||||
if self.employee_id.user_id != self.env.user and self._origin.employee_id != self.employee_id:
|
||||
self.holiday_status_id = False
|
||||
|
||||
@api.onchange('date_from', 'date_to', 'employee_id')
|
||||
def _onchange_leave_dates(self):
|
||||
if self.date_from and self.date_to:
|
||||
self.number_of_days = self._get_number_of_days(self.date_from, self.date_to, self.employee_id.id)['days']
|
||||
else:
|
||||
self.number_of_days = 0
|
||||
@api.depends('date_from', 'date_to', 'employee_id')
|
||||
def _compute_number_of_days(self):
|
||||
for holiday in self:
|
||||
if holiday.date_from and holiday.date_to:
|
||||
holiday.number_of_days = holiday._get_number_of_days(holiday.date_from, holiday.date_to, holiday.employee_id.id)['days']
|
||||
else:
|
||||
holiday.number_of_days = 0
|
||||
|
||||
@api.depends('tz')
|
||||
@api.depends_context('uid')
|
||||
@@ -459,12 +452,13 @@ class HolidaysRequest(models.Model):
|
||||
@api.depends('request_unit_custom', 'employee_id', 'holiday_type', 'department_id.company_id.resource_calendar_id.tz', 'mode_company_id.resource_calendar_id.tz')
|
||||
def _compute_tz(self):
|
||||
for leave in self:
|
||||
tz = False
|
||||
if leave.request_unit_custom:
|
||||
tz = 'UTC' # custom -> already in UTC
|
||||
elif leave.holiday_type == 'employee':
|
||||
tz = leave.employee_id.tz
|
||||
elif leave.holiday_type == 'department':
|
||||
tz= leave.department_id.company_id.resource_calendar_id.tz
|
||||
tz = leave.department_id.company_id.resource_calendar_id.tz
|
||||
elif leave.holiday_type == 'company':
|
||||
tz = leave.mode_company_id.resource_calendar_id.tz
|
||||
leave.tz = tz or self.env.company.resource_calendar_id.tz or self.env.user.tz or 'UTC'
|
||||
@@ -710,15 +704,7 @@ class HolidaysRequest(models.Model):
|
||||
self._check_double_validation_rules(employee_id, values.get('state', False))
|
||||
|
||||
holiday = super(HolidaysRequest, self.with_context(mail_create_nosubscribe=True)).create(values)
|
||||
if self._context.get('import_file'):
|
||||
holiday._onchange_leave_dates()
|
||||
if not self._context.get('leave_fast_create'):
|
||||
# FIXME remove these, as they should not be needed
|
||||
if employee_id:
|
||||
holiday.with_user(SUPERUSER_ID)._sync_employee_details()
|
||||
if 'number_of_days' not in values and ('date_from' in values or 'date_to' in values):
|
||||
holiday.with_user(SUPERUSER_ID)._onchange_leave_dates()
|
||||
|
||||
# Everything that is done here must be done using sudo because we might
|
||||
# have different create and write rights
|
||||
# eg : holidays_user can create a leave request with validation_type = 'manager' for someone else
|
||||
@@ -762,9 +748,6 @@ class HolidaysRequest(models.Model):
|
||||
for holiday in self:
|
||||
if employee_id:
|
||||
holiday.add_follower(employee_id)
|
||||
self._sync_employee_details()
|
||||
if 'number_of_days' not in values and ('date_from' in values or 'date_to' in values):
|
||||
holiday._onchange_leave_dates()
|
||||
return result
|
||||
|
||||
def unlink(self):
|
||||
@@ -924,7 +907,7 @@ class HolidaysRequest(models.Model):
|
||||
|
||||
def action_validate(self):
|
||||
current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
|
||||
if any(holiday.state not in ['confirm', 'validate1'] for holiday in self):
|
||||
if any(holiday.state not in ['confirm', 'validate1'] and holiday.validation_type != 'no_validation' for holiday in self):
|
||||
raise UserError(_('Time off request must be confirmed in order to approve it.'))
|
||||
|
||||
self.write({'state': 'validate'})
|
||||
@@ -969,7 +952,8 @@ class HolidaysRequest(models.Model):
|
||||
'date_to': holiday.date_from.date() + timedelta(days=-1),
|
||||
})[0]
|
||||
before_leave = self.env['hr.leave'].new(before_leave_vals)
|
||||
before_leave._onchange_request_parameters()
|
||||
before_leave._compute_date_from_to()
|
||||
|
||||
# Could happen for part-time contract, that time off is not necessary
|
||||
# anymore.
|
||||
# Imagine you work on monday-wednesday-friday only.
|
||||
@@ -992,7 +976,7 @@ class HolidaysRequest(models.Model):
|
||||
'date_to': conflicting_leave.date_to.date(),
|
||||
})[0]
|
||||
after_leave = self.env['hr.leave'].new(after_leave_vals)
|
||||
after_leave._onchange_request_parameters()
|
||||
after_leave._compute_date_from_to()
|
||||
# Could happen for part-time contract, that time off is not necessary
|
||||
# anymore.
|
||||
if after_leave.date_from < after_leave.date_to:
|
||||
@@ -1004,7 +988,7 @@ class HolidaysRequest(models.Model):
|
||||
for split_leave in split_leaves:
|
||||
if target_state == 'draft':
|
||||
continue
|
||||
if target_state == 'confirm':
|
||||
if target_state == 'confirm' and split_leave.state == 'draft':
|
||||
split_leave.action_confirm()
|
||||
elif target_state == 'validate1':
|
||||
split_leave.action_confirm()
|
||||
|
||||
@@ -26,9 +26,6 @@ class HolidaysAllocation(models.Model):
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_mail_post_access = 'read'
|
||||
|
||||
def _default_employee(self):
|
||||
return self.env.context.get('default_employee_id') or self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
|
||||
|
||||
def _default_holiday_status_id(self):
|
||||
if self.user_has_groups('hr_holidays.group_hr_holidays_user'):
|
||||
domain = [('valid', '=', True)]
|
||||
@@ -59,20 +56,20 @@ class HolidaysAllocation(models.Model):
|
||||
'Start Date', readonly=True, index=True, copy=False, default=fields.Date.context_today,
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, tracking=True)
|
||||
date_to = fields.Datetime(
|
||||
'End Date', readonly=True, copy=False,
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, tracking=True)
|
||||
'End Date', compute='_compute_from_holiday_status_id', store=True, readonly=False, copy=False, tracking=True,
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
|
||||
holiday_status_id = fields.Many2one(
|
||||
"hr.leave.type", string="Time Off Type", required=True, readonly=True,
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
|
||||
domain=_holiday_status_id_domain, default=_default_holiday_status_id)
|
||||
"hr.leave.type", compute='_compute_from_employee_id', store=True, string="Time Off Type", required=True, readonly=False,
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]},
|
||||
domain=_holiday_status_id_domain)
|
||||
employee_id = fields.Many2one(
|
||||
'hr.employee', string='Employee', index=True, readonly=True, ondelete="restrict",
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=_default_employee, tracking=True)
|
||||
manager_id = fields.Many2one('hr.employee', string='Manager', readonly=True)
|
||||
'hr.employee', compute='_compute_from_holiday_type', store=True, string='Employee', index=True, readonly=False, ondelete="restrict", tracking=True,
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
|
||||
manager_id = fields.Many2one('hr.employee', compute='_compute_from_employee_id', store=True, string='Manager')
|
||||
notes = fields.Text('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
|
||||
# duration
|
||||
number_of_days = fields.Float(
|
||||
'Number of Days', tracking=True, default=1,
|
||||
'Number of Days', compute='_compute_from_holiday_status_id', store=True, readonly=False, tracking=True,
|
||||
help='Duration in days. Reference field to use when necessary.')
|
||||
number_of_days_display = fields.Float(
|
||||
'Duration (days)', compute='_compute_number_of_days_display',
|
||||
@@ -109,14 +106,14 @@ class HolidaysAllocation(models.Model):
|
||||
"\n- By Department: all employees of the specified department"
|
||||
"\n- By Employee Tag: all employees of the specific employee group category")
|
||||
mode_company_id = fields.Many2one(
|
||||
'res.company', string='Company Mode', readonly=True,
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
|
||||
'res.company', compute='_compute_from_holiday_type', store=True, string='Company Mode', readonly=False,
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
|
||||
department_id = fields.Many2one(
|
||||
'hr.department', string='Department', readonly=True,
|
||||
'hr.department', compute='_compute_department_id', store=True, string='Department',
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
|
||||
category_id = fields.Many2one(
|
||||
'hr.employee.category', string='Employee Tag', readonly=True,
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
|
||||
'hr.employee.category', compute='_compute_from_holiday_type', store=True, string='Employee Tag', readonly=False,
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
|
||||
# accrual configuration
|
||||
allocation_type = fields.Selection(
|
||||
[
|
||||
@@ -125,18 +122,22 @@ class HolidaysAllocation(models.Model):
|
||||
], string="Allocation Type", default="regular", required=True, readonly=True,
|
||||
states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
|
||||
accrual_limit = fields.Integer('Balance limit', default=0, help="Maximum of allocation for accrual; 0 means no maximum.")
|
||||
number_per_interval = fields.Float("Number of unit per interval", readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=1)
|
||||
interval_number = fields.Integer("Number of unit between two intervals", readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=1)
|
||||
number_per_interval = fields.Float("Number of unit per interval", compute='_compute_from_holiday_status_id', store=True, readonly=False,
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
|
||||
interval_number = fields.Integer("Number of unit between two intervals", compute='_compute_from_holiday_status_id', store=True, readonly=False,
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
|
||||
unit_per_interval = fields.Selection([
|
||||
('hours', 'Hours'),
|
||||
('days', 'Days')
|
||||
], string="Unit of time added at each interval", default='hours', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
|
||||
], compute='_compute_from_holiday_status_id', store=True, string="Unit of time added at each interval", readonly=False,
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
|
||||
interval_unit = fields.Selection([
|
||||
('days', 'Days'),
|
||||
('weeks', 'Weeks'),
|
||||
('months', 'Months'),
|
||||
('years', 'Years')
|
||||
], string="Unit of time between two intervals", default='weeks', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
|
||||
], compute='_compute_from_holiday_status_id', store=True, string="Unit of time between two intervals", readonly=False,
|
||||
states={'cancel': [('readonly', True)], 'refuse': [('readonly', True)], 'validate1': [('readonly', True)], 'validate': [('readonly', True)]})
|
||||
nextcall = fields.Date("Date of the next accrual allocation", default=False, readonly=True)
|
||||
max_leaves = fields.Float(compute='_compute_leaves')
|
||||
leaves_taken = fields.Float(compute='_compute_leaves')
|
||||
@@ -294,66 +295,81 @@ class HolidaysAllocation(models.Model):
|
||||
else:
|
||||
allocation.can_approve = True
|
||||
|
||||
@api.onchange('number_of_hours_display')
|
||||
def _onchange_number_of_hours_display(self):
|
||||
@api.depends('holiday_type')
|
||||
def _compute_from_holiday_type(self):
|
||||
for allocation in self:
|
||||
allocation.number_of_days = allocation.number_of_hours_display / (allocation.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY)
|
||||
if allocation.holiday_type == 'employee':
|
||||
if not allocation.employee_id:
|
||||
allocation.employee_id = self.env.user.employee_id
|
||||
allocation.mode_company_id = False
|
||||
allocation.category_id = False
|
||||
if allocation.holiday_type == 'company':
|
||||
allocation.employee_id = False
|
||||
if not allocation.mode_company_id:
|
||||
allocation.mode_company_id = self.env.user.company_id
|
||||
allocation.category_id = False
|
||||
elif allocation.holiday_type == 'department':
|
||||
allocation.employee_id = False
|
||||
allocation.mode_company_id = False
|
||||
allocation.category_id = False
|
||||
elif allocation.holiday_type == 'category':
|
||||
allocation.employee_id = False
|
||||
allocation.mode_company_id = False
|
||||
elif not allocation.employee_id and not allocation._origin.employee_id:
|
||||
allocation.employee_id = self.env.context.get('default_employee_id') or self.env.user.employee_id
|
||||
|
||||
@api.onchange('number_of_days_display')
|
||||
def _onchange_number_of_days_display(self):
|
||||
@api.depends('holiday_type', 'employee_id')
|
||||
def _compute_department_id(self):
|
||||
for allocation in self:
|
||||
allocation.number_of_days = allocation.number_of_days_display
|
||||
if allocation.holiday_type == 'employee':
|
||||
allocation.department_id = allocation.employee_id.department_id
|
||||
elif allocation.holiday_type == 'department':
|
||||
if not allocation.department_id:
|
||||
allocation.department_id = self.env.user.employee_id.department_id
|
||||
elif allocation.holiday_type == 'category':
|
||||
allocation.department_id = False
|
||||
|
||||
@api.onchange('holiday_type')
|
||||
def _onchange_type(self):
|
||||
if self.holiday_type == 'employee':
|
||||
if not self.employee_id:
|
||||
self.employee_id = self.env.user.employee_id.id
|
||||
self.mode_company_id = False
|
||||
self.category_id = False
|
||||
elif self.holiday_type == 'company':
|
||||
self.employee_id = False
|
||||
if not self.mode_company_id:
|
||||
self.mode_company_id = self.env.user.company_id.id
|
||||
self.category_id = False
|
||||
elif self.holiday_type == 'department':
|
||||
self.employee_id = False
|
||||
self.mode_company_id = False
|
||||
self.category_id = False
|
||||
if not self.department_id:
|
||||
self.department_id = self.env.user.employee_id.department_id.id
|
||||
elif self.holiday_type == 'category':
|
||||
self.employee_id = False
|
||||
self.mode_company_id = False
|
||||
self.department_id = False
|
||||
@api.depends('employee_id')
|
||||
def _compute_from_employee_id(self):
|
||||
default_holiday_status_id = self._default_holiday_status_id()
|
||||
for holiday in self:
|
||||
holiday.manager_id = holiday.employee_id and holiday.employee_id.parent_id
|
||||
if holiday.employee_id.user_id != self.env.user and holiday._origin.employee_id != holiday.employee_id:
|
||||
holiday.holiday_status_id = False
|
||||
elif not holiday.holiday_status_id and not holiday._origin.holiday_status_id:
|
||||
holiday.holiday_status_id = default_holiday_status_id
|
||||
|
||||
@api.onchange('employee_id')
|
||||
def _onchange_employee(self):
|
||||
self.manager_id = self.employee_id and self.employee_id.parent_id
|
||||
if self.employee_id.user_id != self.env.user and self._origin.employee_id != self.employee_id:
|
||||
self.holiday_status_id = False
|
||||
if self.holiday_type == 'employee':
|
||||
self.department_id = self.employee_id.department_id
|
||||
@api.depends('holiday_status_id', 'allocation_type')
|
||||
def _compute_from_holiday_status_id(self):
|
||||
for allocation in self:
|
||||
# set default values
|
||||
if not allocation.number_of_days and not allocation._origin.number_of_days:
|
||||
allocation.number_of_days = 1
|
||||
if not allocation.interval_number and not allocation._origin.interval_number:
|
||||
allocation.interval_number = 1
|
||||
if not allocation.number_per_interval and not allocation._origin.number_per_interval:
|
||||
allocation.number_per_interval = 1
|
||||
if not allocation.unit_per_interval and not allocation._origin.unit_per_interval:
|
||||
allocation.unit_per_interval = 'hours'
|
||||
if not allocation.interval_unit and not allocation._origin.interval_unit:
|
||||
allocation.interval_unit = 'weeks'
|
||||
|
||||
@api.onchange('holiday_status_id')
|
||||
def _onchange_holiday_status_id(self):
|
||||
if self.holiday_status_id.validity_stop and self.date_to:
|
||||
new_date_to = datetime.combine(self.holiday_status_id.validity_stop, time.max)
|
||||
if new_date_to < self.date_to:
|
||||
self.date_to = new_date_to
|
||||
if allocation.holiday_status_id.validity_stop and allocation.date_to:
|
||||
new_date_to = datetime.combine(allocation.holiday_status_id.validity_stop, time.max)
|
||||
if new_date_to < allocation.date_to:
|
||||
allocation.date_to = new_date_to
|
||||
|
||||
if self.allocation_type == 'accrual':
|
||||
self.number_of_days = 0
|
||||
|
||||
if self.holiday_status_id.request_unit == 'hour':
|
||||
self.unit_per_interval = 'hours'
|
||||
if allocation.allocation_type == 'accrual':
|
||||
allocation.number_of_days = 0
|
||||
if allocation.holiday_status_id.request_unit == 'hour':
|
||||
allocation.unit_per_interval = 'hours'
|
||||
else:
|
||||
allocation.unit_per_interval = 'days'
|
||||
else:
|
||||
self.unit_per_interval = 'days'
|
||||
else:
|
||||
self.interval_number = 1
|
||||
self.interval_unit = 'weeks'
|
||||
self.number_per_interval = 1
|
||||
self.unit_per_interval = 'hours'
|
||||
allocation.interval_number = 1
|
||||
allocation.interval_unit = 'weeks'
|
||||
allocation.number_per_interval = 1
|
||||
allocation.unit_per_interval = 'hours'
|
||||
|
||||
####################################################
|
||||
# ORM Overrides methods
|
||||
@@ -416,8 +432,6 @@ class HolidaysAllocation(models.Model):
|
||||
self._check_approval_update(values['state'])
|
||||
result = super(HolidaysAllocation, self).write(values)
|
||||
self.add_follower(employee_id)
|
||||
if 'employee_id' in values:
|
||||
self._onchange_employee()
|
||||
return result
|
||||
|
||||
def unlink(self):
|
||||
|
||||
@@ -107,9 +107,10 @@ class TestAcessRightsStates(TestHrHolidaysAccessRightsCommon):
|
||||
'name': 'Ranoi',
|
||||
'employee_id': self.employee_emp.id,
|
||||
'holiday_status_id': status.id,
|
||||
'state': 'draft'
|
||||
}
|
||||
leave = self.request_leave(1, datetime.today() + relativedelta(days=20 + i), 1, values)
|
||||
# the state has to be set to draft in a write because it is initialized to confirm if it has validation
|
||||
leave.write({'state': 'draft'})
|
||||
with self.assertRaises(UserError):
|
||||
leave.action_draft()
|
||||
|
||||
@@ -399,17 +400,6 @@ class TestAccessRightsCreate(TestHrHolidaysAccessRightsCommon):
|
||||
with self.assertRaises(AccessError):
|
||||
self.request_leave(self.user_employee_id, datetime.today() + relativedelta(days=5), 1, values)
|
||||
|
||||
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
|
||||
def test_base_user_create_validate(self):
|
||||
""" A simple user cannot create a leave in validate state """
|
||||
values = {
|
||||
'name': 'Hol10',
|
||||
'employee_id': self.employee_emp_id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'state': 'validate',
|
||||
}
|
||||
with self.assertRaises(ValidationError):
|
||||
self.request_leave(self.user_employee_id, datetime.today() + relativedelta(days=5), 1, values)
|
||||
|
||||
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
|
||||
def test_base_user_create_batch(self):
|
||||
@@ -445,22 +435,6 @@ class TestAccessRightsCreate(TestHrHolidaysAccessRightsCommon):
|
||||
}
|
||||
self.request_leave(self.user_hruser_id, datetime.today() + relativedelta(days=5), 1, values)
|
||||
|
||||
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
|
||||
def test_holidays_user_create_validate(self):
|
||||
""" A holidays user cannot create a leave in validate state """
|
||||
values = {
|
||||
'name': 'Hol10',
|
||||
'employee_id': self.employee_emp_id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'state': 'validate',
|
||||
}
|
||||
with self.assertRaises(ValidationError):
|
||||
self.request_leave(self.user_hruser_id, datetime.today() + relativedelta(days=5), 1, values)
|
||||
values.update(employee_id=self.employee_hruser_id)
|
||||
with self.assertRaises(ValidationError):
|
||||
self.request_leave(self.user_hruser_id, datetime.today() + relativedelta(days=5), 1, values)
|
||||
|
||||
|
||||
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
|
||||
def test_holidays_user_create_batch(self):
|
||||
""" A holidays user cannot create a leave in bacth mode (by company, by department, by tag)"""
|
||||
@@ -495,21 +469,6 @@ class TestAccessRightsCreate(TestHrHolidaysAccessRightsCommon):
|
||||
}
|
||||
self.request_leave(self.user_hrmanager_id, datetime.today() + relativedelta(days=5), 1, values)
|
||||
|
||||
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
|
||||
def test_holidays_manager_create_validate(self):
|
||||
""" A holidays manager cannot create a leave in validate state """
|
||||
values = {
|
||||
'name': 'Hol10',
|
||||
'employee_id': self.employee_emp_id,
|
||||
'holiday_status_id': self.leave_type.id,
|
||||
'state': 'validate',
|
||||
}
|
||||
with self.assertRaises(ValidationError):
|
||||
self.request_leave(self.user_hrmanager_id, datetime.today() + relativedelta(days=5), 1, values)
|
||||
values.update(employee_id=self.employee_hruser_id)
|
||||
with self.assertRaises(ValidationError):
|
||||
self.request_leave(self.user_hrmanager_id, datetime.today() + relativedelta(days=5), 1, values)
|
||||
|
||||
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
|
||||
def test_holidays_manager_create_batch(self):
|
||||
""" A holidays manager can create a leave in bacth mode (by company, by department, by tag)"""
|
||||
|
||||
@@ -48,7 +48,7 @@ class TestCompanyLeave(SavepointCase):
|
||||
'date_to': date(2020, 1, 9),
|
||||
'number_of_days': 3,
|
||||
})
|
||||
leave._onchange_request_parameters()
|
||||
leave._compute_date_from_to()
|
||||
|
||||
company_leave = self.env['hr.leave'].create({
|
||||
'name': 'Bank Holiday',
|
||||
@@ -61,7 +61,7 @@ class TestCompanyLeave(SavepointCase):
|
||||
'request_date_to': date(2020, 1, 8),
|
||||
'number_of_days': 1,
|
||||
})
|
||||
company_leave._onchange_request_parameters()
|
||||
company_leave._compute_date_from_to()
|
||||
|
||||
company_leave.action_validate()
|
||||
|
||||
@@ -102,7 +102,7 @@ class TestCompanyLeave(SavepointCase):
|
||||
'date_to': date(2020, 1, 9),
|
||||
'number_of_days': 3,
|
||||
})
|
||||
leave._onchange_request_parameters()
|
||||
leave._compute_date_from_to()
|
||||
|
||||
company_leave = self.env['hr.leave'].create({
|
||||
'name': 'Bank Holiday',
|
||||
@@ -115,7 +115,7 @@ class TestCompanyLeave(SavepointCase):
|
||||
'request_date_to': date(2020, 1, 8),
|
||||
'number_of_days': 1,
|
||||
})
|
||||
company_leave._onchange_request_parameters()
|
||||
company_leave._compute_date_from_to()
|
||||
|
||||
company_leave.action_validate()
|
||||
|
||||
@@ -156,7 +156,7 @@ class TestCompanyLeave(SavepointCase):
|
||||
'request_date_from_period': 'am',
|
||||
|
||||
})
|
||||
leave._onchange_request_parameters()
|
||||
leave._compute_date_from_to()
|
||||
|
||||
company_leave = self.env['hr.leave'].create({
|
||||
'name': 'Bank Holiday',
|
||||
@@ -169,7 +169,7 @@ class TestCompanyLeave(SavepointCase):
|
||||
'request_date_to': date(2020, 1, 7),
|
||||
'number_of_days': 1,
|
||||
})
|
||||
company_leave._onchange_request_parameters()
|
||||
company_leave._compute_date_from_to()
|
||||
|
||||
company_leave.action_validate()
|
||||
|
||||
@@ -198,7 +198,7 @@ class TestCompanyLeave(SavepointCase):
|
||||
'number_of_days': 1,
|
||||
|
||||
})
|
||||
leave._onchange_request_parameters()
|
||||
leave._compute_date_from_to()
|
||||
|
||||
company_leave = self.env['hr.leave'].create({
|
||||
'name': 'Bank Holiday',
|
||||
@@ -211,7 +211,7 @@ class TestCompanyLeave(SavepointCase):
|
||||
'request_date_to': date(2020, 1, 9),
|
||||
'number_of_days': 1,
|
||||
})
|
||||
company_leave._onchange_request_parameters()
|
||||
company_leave._compute_date_from_to()
|
||||
|
||||
company_leave.action_validate()
|
||||
|
||||
@@ -248,7 +248,7 @@ class TestCompanyLeave(SavepointCase):
|
||||
'request_date_to': date(2020, 1, 10),
|
||||
'number_of_days': 3,
|
||||
})
|
||||
leave._onchange_request_parameters()
|
||||
leave._compute_date_from_to()
|
||||
|
||||
company_leave = self.env['hr.leave'].create({
|
||||
'name': 'Bank Holiday',
|
||||
@@ -261,7 +261,7 @@ class TestCompanyLeave(SavepointCase):
|
||||
'request_date_to': date(2020, 1, 10),
|
||||
'number_of_days': 1,
|
||||
})
|
||||
company_leave._onchange_request_parameters()
|
||||
company_leave._compute_date_from_to()
|
||||
company_leave.action_validate()
|
||||
|
||||
all_leaves = self.env['hr.leave'].search([('employee_id', '=', self.employee.id)], order='id')
|
||||
|
||||
@@ -210,7 +210,6 @@ class TestLeaveRequests(TestHrHolidaysCommon):
|
||||
'request_hour_from': '8', # 8:00 AM in the employee's timezone
|
||||
'request_hour_to': '17', # 5:00 PM in the employee's timezone
|
||||
})
|
||||
leave._onchange_request_parameters()
|
||||
self.assertEqual(leave.date_from, datetime(2019, 5, 5, 20, 0, 0), "It should have been localized before saving in UTC")
|
||||
self.assertEqual(leave.date_to, datetime(2019, 5, 6, 5, 0, 0), "It should have been localized before saving in UTC")
|
||||
|
||||
@@ -230,7 +229,6 @@ class TestLeaveRequests(TestHrHolidaysCommon):
|
||||
'request_hour_from': '8', # 8:00 AM in the company's timezone
|
||||
'request_hour_to': '17', # 5:00 PM in the company's timezone
|
||||
})
|
||||
leave._onchange_request_parameters()
|
||||
self.assertEqual(leave.date_from, datetime(2019, 5, 5, 20, 0, 0), "It should have been localized before saving in UTC")
|
||||
self.assertEqual(leave.date_to, datetime(2019, 5, 6, 5, 0, 0), "It should have been localized before saving in UTC")
|
||||
|
||||
@@ -251,7 +249,6 @@ class TestLeaveRequests(TestHrHolidaysCommon):
|
||||
'request_hour_from': '8', # 8:00 AM in the department's timezone
|
||||
'request_hour_to': '17', # 5:00 PM in the department's timezone
|
||||
})
|
||||
leave._onchange_request_parameters()
|
||||
self.assertEqual(leave.date_from, datetime(2019, 5, 5, 20, 0, 0), "It should have been localized before saving in UTC")
|
||||
self.assertEqual(leave.date_to, datetime(2019, 5, 6, 5, 0, 0), "It should have been localized before saving in UTC")
|
||||
|
||||
@@ -394,7 +391,6 @@ class TestLeaveRequests(TestHrHolidaysCommon):
|
||||
'holiday_status_id': self.holidays_type_1.id,
|
||||
})
|
||||
leave = self.env['hr.leave'].with_user(self.user_employee_id).new(values)
|
||||
leave._onchange_request_parameters()
|
||||
self.assertEqual(leave.number_of_days, number_of_days)
|
||||
|
||||
@mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail')
|
||||
|
||||
@@ -6,14 +6,17 @@ from odoo import api, fields, models, tools
|
||||
class MaintenanceEquipment(models.Model):
|
||||
_inherit = 'maintenance.equipment'
|
||||
|
||||
employee_id = fields.Many2one('hr.employee', string='Assigned Employee', tracking=True)
|
||||
department_id = fields.Many2one('hr.department', string='Assigned Department', tracking=True)
|
||||
employee_id = fields.Many2one('hr.employee', compute='_compute_equipment_assign',
|
||||
store=True, readonly=False, string='Assigned Employee', tracking=True)
|
||||
department_id = fields.Many2one('hr.department', compute='_compute_equipment_assign',
|
||||
store=True, readonly=False, string='Assigned Department', tracking=True)
|
||||
equipment_assign_to = fields.Selection(
|
||||
[('department', 'Department'), ('employee', 'Employee'), ('other', 'Other')],
|
||||
string='Used By',
|
||||
required=True,
|
||||
default='employee')
|
||||
owner_user_id = fields.Many2one(compute='_compute_owner', store=True)
|
||||
assign_date = fields.Date(compute='_compute_equipement_assign', store=True, readonly=False, copy=True)
|
||||
|
||||
@api.depends('employee_id', 'department_id', 'equipment_assign_to')
|
||||
def _compute_owner(self):
|
||||
@@ -24,13 +27,19 @@ class MaintenanceEquipment(models.Model):
|
||||
elif equipment.equipment_assign_to == 'department':
|
||||
equipment.owner_user_id = equipment.department_id.manager_id.user_id.id
|
||||
|
||||
@api.onchange('equipment_assign_to')
|
||||
def _onchange_equipment_assign_to(self):
|
||||
if self.equipment_assign_to == 'employee':
|
||||
self.department_id = False
|
||||
if self.equipment_assign_to == 'department':
|
||||
self.employee_id = False
|
||||
self.assign_date = fields.Date.context_today(self)
|
||||
@api.depends('equipment_assign_to')
|
||||
def _compute_equipment_assign(self):
|
||||
for equipment in self:
|
||||
if equipment.equipment_assign_to == 'employee':
|
||||
equipment.department_id = False
|
||||
equipment.employee_id = equipment.employee_id
|
||||
elif equipment.equipment_assign_to == 'department':
|
||||
equipment.employee_id = False
|
||||
equipment.department_id = equipment.department_id
|
||||
else:
|
||||
equipment.department_id = equipment.department_id
|
||||
equipment.employee_id = equipment.employee_id
|
||||
equipment.assign_date = fields.Date.context_today(self)
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
|
||||
@@ -98,7 +98,8 @@ class Applicant(models.Model):
|
||||
name = fields.Char("Subject / Application Name", required=True)
|
||||
active = fields.Boolean("Active", default=True, help="If the active field is set to false, it will allow you to hide the case without removing it.")
|
||||
description = fields.Text("Description")
|
||||
email_from = fields.Char("Email", size=128, help="Applicant email", compute='_compute_partner_phone_email', store=True, readonly=False)
|
||||
email_from = fields.Char("Email", size=128, help="Applicant email", compute='_compute_partner_phone_email',
|
||||
inverse='_inverse_partner_email', store=True)
|
||||
probability = fields.Float("Probability")
|
||||
partner_id = fields.Many2one('res.partner', "Contact", copy=False)
|
||||
create_date = fields.Datetime("Creation Date", readonly=True, index=True)
|
||||
@@ -125,8 +126,10 @@ class Applicant(models.Model):
|
||||
salary_expected = fields.Float("Expected Salary", group_operator="avg", help="Salary Expected by Applicant", tracking=True)
|
||||
availability = fields.Date("Availability", help="The date at which the applicant will be available to start working", tracking=True)
|
||||
partner_name = fields.Char("Applicant's Name")
|
||||
partner_phone = fields.Char("Phone", size=32, compute='_compute_partner_phone_email', store=True, readonly=False)
|
||||
partner_mobile = fields.Char("Mobile", size=32, compute='_compute_partner_phone_email', store=True, readonly=False)
|
||||
partner_phone = fields.Char("Phone", size=32, compute='_compute_partner_phone_email',
|
||||
inverse='_inverse_partner_phone', store=True)
|
||||
partner_mobile = fields.Char("Mobile", size=32, compute='_compute_partner_phone_email',
|
||||
inverse='_inverse_partner_mobile', store=True)
|
||||
type_id = fields.Many2one('hr.recruitment.degree', "Degree")
|
||||
department_id = fields.Many2one(
|
||||
'hr.department', "Department", compute='_compute_department', store=True, readonly=False,
|
||||
@@ -240,22 +243,6 @@ class Applicant(models.Model):
|
||||
for applicant in self.filtered(lambda a: a.job_id):
|
||||
applicant.user_id = applicant.job_id.user_id.id
|
||||
|
||||
|
||||
@api.onchange('email_from')
|
||||
def onchange_email_from(self):
|
||||
if self.partner_id and self.email_from and not self.partner_id.email:
|
||||
self.partner_id.email = self.email_from
|
||||
|
||||
@api.onchange('partner_phone')
|
||||
def onchange_partner_phone(self):
|
||||
if self.partner_id and self.partner_phone and not self.partner_id.phone:
|
||||
self.partner_id.phone = self.partner_phone
|
||||
|
||||
@api.onchange('partner_mobile')
|
||||
def onchange_partner_mobile(self):
|
||||
if self.partner_id and self.partner_mobile and not self.partner_id.mobile:
|
||||
self.partner_id.mobile = self.partner_mobile
|
||||
|
||||
@api.depends('partner_id')
|
||||
def _compute_partner_phone_email(self):
|
||||
for applicant in self:
|
||||
@@ -263,6 +250,18 @@ class Applicant(models.Model):
|
||||
applicant.partner_mobile = applicant.partner_id.mobile
|
||||
applicant.email_from = applicant.partner_id.email
|
||||
|
||||
def _inverse_partner_email(self):
|
||||
for applicant in self.filtered(lambda a: a.partner_id and a.email_from and not a.partner_id.email):
|
||||
applicant.partner_id.email = applicant.email_from
|
||||
|
||||
def _inverse_partner_phone(self):
|
||||
for applicant in self.filtered(lambda a: a.partner_id and a.partner_phone and not a.partner_id.phone):
|
||||
applicant.partner_id.phone = applicant.partner_phone
|
||||
|
||||
def _inverse_partner_mobile(self):
|
||||
for applicant in self.filtered(lambda a: a.partner_id and a.partner_mobile and not a.partner_id.mobile):
|
||||
applicant.partner_id.mobile = applicant.partner_mobile
|
||||
|
||||
@api.depends('stage_id')
|
||||
def _compute_date_closed(self):
|
||||
for applicant in self:
|
||||
@@ -438,6 +437,7 @@ class Applicant(models.Model):
|
||||
'phone': applicant.partner_phone,
|
||||
'mobile': applicant.partner_mobile
|
||||
})
|
||||
applicant.partner_id = new_partner_id
|
||||
address_id = new_partner_id.address_get(['contact'])['contact']
|
||||
if applicant.partner_name or contact_name:
|
||||
employee = self.env['hr.employee'].create({
|
||||
|
||||
@@ -16,8 +16,8 @@ class HrWorkEntry(models.Model):
|
||||
active = fields.Boolean(default=True)
|
||||
employee_id = fields.Many2one('hr.employee', required=True, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
date_start = fields.Datetime(required=True, string='From')
|
||||
date_stop = fields.Datetime(string='To')
|
||||
duration = fields.Float(compute='_compute_duration', inverse='_inverse_duration', store=True, string="Period")
|
||||
date_stop = fields.Datetime(compute='_compute_date_stop', store=True, readonly=False, string='To')
|
||||
duration = fields.Float(compute='_compute_duration', store=True, string="Period")
|
||||
work_entry_type_id = fields.Many2one('hr.work.entry.type')
|
||||
color = fields.Integer(related='work_entry_type_id.color', readonly=True)
|
||||
state = fields.Selection([
|
||||
@@ -40,19 +40,15 @@ class HrWorkEntry(models.Model):
|
||||
for rec in self:
|
||||
rec.conflict = rec.state == 'conflict'
|
||||
|
||||
@api.onchange('duration')
|
||||
def _onchange_duration(self):
|
||||
self._inverse_duration()
|
||||
|
||||
@api.depends('date_stop', 'date_start')
|
||||
def _compute_duration(self):
|
||||
for work_entry in self:
|
||||
work_entry.duration = work_entry._get_duration(work_entry.date_start, work_entry.date_stop)
|
||||
|
||||
def _inverse_duration(self):
|
||||
for work_entry in self:
|
||||
if work_entry.date_start and work_entry.duration:
|
||||
work_entry.date_stop = work_entry.date_start + relativedelta(hours=work_entry.duration)
|
||||
@api.depends('date_start', 'duration')
|
||||
def _compute_date_stop(self):
|
||||
for work_entry in self.filtered(lambda w: w.date_start and w.duration):
|
||||
work_entry.date_stop = work_entry.date_start + relativedelta(hours=work_entry.duration)
|
||||
|
||||
def _get_duration(self, date_start, date_stop):
|
||||
if not date_start or not date_stop:
|
||||
|
||||
@@ -7,29 +7,29 @@ from odoo import api, fields, models
|
||||
class Expense(models.Model):
|
||||
_inherit = "hr.expense"
|
||||
|
||||
sale_order_id = fields.Many2one('sale.order', string='Reinvoice Customer', readonly=True, tracking=True,
|
||||
states={'draft': [('readonly', False)], 'reported': [('readonly', False)]},
|
||||
sale_order_id = fields.Many2one('sale.order', compute='_compute_sale_order_id', store=True, string='Reinvoice Customer', readonly=False, tracking=True,
|
||||
states={'approved': [('readonly', True)], 'done': [('readonly', True)], 'refused': [('readonly', True)]},
|
||||
# NOTE: only confirmed SO can be selected, but this domain in activated throught the name search with the `sale_expense_all_order`
|
||||
# context key. So, this domain is not the one applied.
|
||||
domain="[('state', '=', 'sale'), ('company_id', '=', company_id)]",
|
||||
help="If the product has an expense policy, it will be reinvoiced on this sales order")
|
||||
can_be_reinvoiced = fields.Boolean("Can be reinvoiced", compute='_compute_can_be_reinvoiced')
|
||||
analytic_account_id = fields.Many2one(compute='_compute_analytic_account_id', store=True, readonly=False)
|
||||
|
||||
@api.depends('product_id')
|
||||
@api.depends('product_id.expense_policy')
|
||||
def _compute_can_be_reinvoiced(self):
|
||||
for expense in self:
|
||||
expense.can_be_reinvoiced = expense.product_id.expense_policy in ['sales_price', 'cost']
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_id(self):
|
||||
super(Expense, self)._onchange_product_id()
|
||||
if not self.can_be_reinvoiced:
|
||||
self.sale_order_id = False
|
||||
@api.depends('can_be_reinvoiced')
|
||||
def _compute_sale_order_id(self):
|
||||
for expense in self.filtered(lambda e: not e.can_be_reinvoiced):
|
||||
expense.sale_order_id = False
|
||||
|
||||
@api.onchange('sale_order_id')
|
||||
def _onchange_sale_order(self):
|
||||
if self.sale_order_id:
|
||||
self.analytic_account_id = self.sale_order_id.sudo().analytic_account_id # `sudo` required for normal employee without sale access rights
|
||||
@api.depends('sale_order_id')
|
||||
def _compute_analytic_account_id(self):
|
||||
for expense in self.filtered('sale_order_id'):
|
||||
expense.analytic_account_id = expense.sale_order_id.sudo().analytic_account_id # `sudo` required for normal employee without sale access rights
|
||||
|
||||
def action_move_create(self):
|
||||
""" When posting expense, if the AA is given, we will track cost in that
|
||||
|
||||
@@ -57,7 +57,6 @@ class TestReInvoice(TestExpenseCommon, TestCommonSaleNoChart):
|
||||
'sale_order_id': self.sale_order.id,
|
||||
'analytic_account_id': self.sale_order.analytic_account_id.id,
|
||||
})
|
||||
expense1._onchange_product_id()
|
||||
expense2 = self.env['hr.expense'].create({
|
||||
'name': 'Expense for delivered product',
|
||||
'employee_id': self.employee.id,
|
||||
@@ -68,7 +67,6 @@ class TestReInvoice(TestExpenseCommon, TestCommonSaleNoChart):
|
||||
'sale_order_id': self.sale_order.id,
|
||||
'analytic_account_id': self.sale_order.analytic_account_id.id,
|
||||
})
|
||||
expense2._onchange_product_id()
|
||||
|
||||
# approve and generate entries
|
||||
self.expense_sheet.approve_expense_sheets()
|
||||
@@ -111,7 +109,6 @@ class TestReInvoice(TestExpenseCommon, TestCommonSaleNoChart):
|
||||
'sale_order_id': self.sale_order.id,
|
||||
'analytic_account_id': self.sale_order.analytic_account_id.id,
|
||||
})
|
||||
expense1._onchange_product_id()
|
||||
|
||||
# approve and generate entries
|
||||
self.expense_sheet.approve_expense_sheets()
|
||||
@@ -158,7 +155,6 @@ class TestReInvoice(TestExpenseCommon, TestCommonSaleNoChart):
|
||||
'sale_order_id': self.sale_order.id,
|
||||
'analytic_account_id': self.sale_order.analytic_account_id.id,
|
||||
})
|
||||
expense1._onchange_product_id()
|
||||
|
||||
# approve and generate entries
|
||||
self.expense_sheet.approve_expense_sheets()
|
||||
@@ -183,7 +179,6 @@ class TestReInvoice(TestExpenseCommon, TestCommonSaleNoChart):
|
||||
'sale_order_id': self.sale_order.id,
|
||||
'analytic_account_id': self.sale_order.analytic_account_id.id,
|
||||
})
|
||||
expense2._onchange_product_id()
|
||||
|
||||
# approve and generate entries
|
||||
self.expense_sheet2.approve_expense_sheets()
|
||||
@@ -218,19 +213,16 @@ class TestReInvoice(TestExpenseCommon, TestCommonSaleNoChart):
|
||||
expense1 = self.env['hr.expense'].create({
|
||||
'name': 'Expense for no expense product',
|
||||
'employee_id': self.employee.id,
|
||||
'product_id': self.product_no_expense.id,
|
||||
'unit_amount': 15,
|
||||
'quantity': 3,
|
||||
'sheet_id': self.expense_sheet.id,
|
||||
'sale_order_id': self.sale_order.id,
|
||||
'analytic_account_id': analytic_account.id,
|
||||
})
|
||||
expense1._onchange_product_id() # will reset the SO field to NULL
|
||||
|
||||
expense1.write({'product_id': self.product_no_expense.id})
|
||||
# approve and generate entries
|
||||
self.expense_sheet.approve_expense_sheets()
|
||||
self.expense_sheet.action_sheet_move_create()
|
||||
|
||||
self.assertFalse(expense1.sale_order_id, "None reinvoicable expense can not be linked to SO")
|
||||
self.assertFalse(self.sale_order.analytic_account_id, "Posting expense with an non expense product should not trigger analytic Account creation from SO, since non reinvoicable expense can not be linked to SO")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user