odex30_standard/exp_payroll_loans/models/reconcile_leaves.py

395 lines
18 KiB
Python

# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from datetime import datetime
class ReconcileLeaves(models.Model):
_name = 'reconcile.leaves'
_inherit = ['mail.thread', 'mail.activity.mixin']
_rec_name = 'employee_id'
_description = 'Reconcile Leaves'
date = fields.Date()
contract_start_date = fields.Date(related='employee_id.contract_id.date_start')
contract_end_date = fields.Date(related='employee_id.contract_id.date_end')
start_vacation_date = fields.Datetime(related='yearly_vacation.date_from')
end_vacation_date = fields.Datetime(related='yearly_vacation.date_to')
leave_duration = fields.Float()
leave_amount = fields.Float()
salary = fields.Float()
total_allowance = fields.Float()
total_deduction = fields.Float()
total_loans = fields.Float()
net = fields.Float()
state = fields.Selection(selection=[
("draft", _("Draft")),
("submit", _("Submit")),
("direct_manager", _("Direct Manager")),
("hr_manager", _("HR Manager")),
("finance_manager", _("Finance Manager")),
("gm_manager", _("GM Manager")),
("pay", _("Pay")),
("refuse", _("Refuse"))], default='draft', tracking=True)
# Relational fields
employee_id = fields.Many2one('hr.employee')
department_id = fields.Many2one('hr.department', related='employee_id.department_id')
job_id = fields.Many2one('hr.job', related='employee_id.job_id')
contract_id = fields.Many2one('hr.contract', related='employee_id.contract_id')
yearly_vacation = fields.Many2one('hr.holidays')
calculation_method = fields.Many2many('hr.salary.rule')
journal_id = fields.Many2one('account.journal')
account_id = fields.Many2one('account.account')
allowance_deduction = fields.One2many('reconcile.allowance.deduction', 'reconcile_id')
journal_entry_ids = fields.One2many('reconcile.allowance.journal', 'journal_entry_ids_lines')
account_move_id = fields.Many2one('account.move')
loans_ids = fields.Many2many(
'hr.loan.salary.advance',
domain="[('employee_id','=','self.employee_id.id'),('state','=','pay'),('months','=','1')]")
def unlink(self):
for item in self:
if item.state != 'draft':
raise UserError(_('You can not delete record in state not in draft'))
return super(ReconcileLeaves, self).unlink()
# To get salary rules from holiday setting
@api.onchange('yearly_vacation')
def _get_salary_rule(self):
for rec in self:
rec.calculation_method = False
if rec.yearly_vacation:
rec.calculation_method = rec.yearly_vacation.holiday_status_id.salary_rules_ids.ids
else:
rec.calculation_method = False
# one reconcile holiday for each holiday request
@api.constrains('yearly_vacation')
def constain_holiday_request(self):
for rec in self:
holdiay_req = self.env['reconcile.leaves'].search([('employee_id', '=', rec.employee_id.id),
('yearly_vacation', '=', rec.yearly_vacation.id)])
for itms in holdiay_req:
if len(holdiay_req) > 1:
raise UserError(
_('This Holiday Request Has Been Reconcile %s') % rec.yearly_vacation.display_name)
# default loans lines and domain
@api.onchange('employee_id')
def _get_loans_ids_and_domain(self):
record_list = []
record_ids = self.env['hr.loan.salary.advance'].search(
[('employee_id', '=', self.employee_id.id), ('state', '=', 'pay'), ('months', '=', '1')])
for item in record_ids:
if item.remaining_loan_amount > 0:
record_list.append(item.id)
for line in self:
line.loans_ids = record_list
domain = [('employee_id', '=', line.employee_id.id), ('state', '=', 'validate1')]
res = {'domain': {'loans_ids': [('id', 'in', record_ids.ids)],
'yearly_vacation': domain}}
return res
# dynamic domain for yearly_vacation when employee_id changed
@api.onchange('employee_id')
def onchange_employee_id(self):
for rec in self:
domain = [('employee_id', '=', rec.employee_id.id), ('state', '=', 'validate1')]
res = {'domain': {'yearly_vacation': domain}}
return res
# compute leave duration when change yearly_vacation
@api.onchange('yearly_vacation', 'calculation_method')
def compute_leave_duration(self):
self.leave_amount, self.leave_duration = 0.0, 0.0
if self.start_vacation_date and self.end_vacation_date:
date_start = fields.Datetime.from_string(self.start_vacation_date)
date_end = fields.Datetime.from_string(self.end_vacation_date)
days_start = date_start.day
days_end = date_end.day
duration = date_end - date_start
if days_start == 31 or days_end == 31: # payroll for 30 days
self.leave_duration = duration.days
else:
self.leave_duration = duration.days + 1
if self.salary:
if self.leave_duration > 0:
self.leave_amount = self.salary / (30 / self.leave_duration)
# Compute employee salary when calculation method changed
@api.onchange('yearly_vacation', 'calculation_method', 'employee_id', 'loans_ids')
def compute_employee_salary_fom_calculation_method(self):
# Initialize fields values
self.salary, self.total_allowance, self.total_deduction, self.total_loans, self.net = 0.0, 0.0, 0.0, 0.0, 0.0
if self.calculation_method:
# initialize variables
total = 0.0
allowance_deduction_list = []
for item in self.calculation_method:
# check if there is a contract
if self.contract_id:
# Check if item in calculation method is deduction or allowance to subtract or add
if item.category_id.rule_type == 'deduction':
total -= self.compute_rule(item, self.contract_id)
else:
total += self.compute_rule(item, self.contract_id)
if self.leave_duration > 0:
record = self.env['reconcile.allowance.deduction'].create({
'salary_rule_id': item.id,
'amount': (self.compute_rule(item, self.contract_id) / (30 / self.leave_duration))})
allowance_deduction_list.append(record.id)
else:
raise UserError(_('Employee "%s" has no contract') % self.employee_id.name)
self.allowance_deduction = self.env['reconcile.allowance.deduction'].browse(allowance_deduction_list)
self.salary = total
# Compute total allowance , total deduction and net
if self.allowance_deduction:
for item in self.allowance_deduction:
if item.category_id.rule_type == 'allowance':
self.total_allowance += item.amount
elif item.category_id.rule_type == 'deduction':
self.total_deduction += item.amount
self.net = self.total_allowance - self.total_deduction
else:
self.salary, self.total_allowance, self.total_deduction, self.net = 0.0, 0.0, 0.0, 0.0
self.allowance_deduction = self.env['reconcile.allowance.deduction'].browse([])
# Compute total loans and net
if self.loans_ids:
for line in self.loans_ids:
self.total_loans += line.remaining_loan_amount
self.net -= self.total_loans
else:
self.total_loans = 0.0
# Compute salary rules
def compute_rule(self, rule, contract):
localdict = dict(employee=contract.employee_id, contract=contract)
if rule.amount_select == 'percentage':
total_percent = 0
if rule.related_benefits_discounts:
for line in rule.related_benefits_discounts:
if line.amount_select == 'fix':
total_percent += self.compute_rule(line, contract)
elif line.amount_select == 'percentage':
total_percent += self.compute_rule(line, contract)
else:
total_percent += self.compute_rule(line, contract)
if total_percent:
if rule.salary_type == 'fixed':
try:
return float(total_percent * rule.amount_percentage / 100)
except:
raise UserError(
_('Wrong percentage base or quantity defined for salary rule %s (%s).') % (
rule.name, rule.code))
elif rule.salary_type == 'related_levels':
levels_ids = rule.salary_amount_ids.filtered(
lambda item: item.salary_scale_level.id == contract.salary_level.id)
if levels_ids:
for l in levels_ids:
try:
return float(l.salary * total_percent / 100)
except:
raise UserError(
_('Wrong quantity defined for salary rule %s (%s).') % (
rule.name, rule.code))
else:
return 0
elif rule.salary_type == 'related_groups':
groups_ids = rule.salary_amount_ids.filtered(
lambda item: item.salary_scale_group.id == contract.salary_group.id)
if groups_ids:
for g in groups_ids:
try:
return float(g.salary * total_percent / 100)
except:
raise UserError(
_('Wrong quantity defined for salary rule %s (%s).') % (
rule.name, rule.code))
else:
return 0
elif rule.salary_type == 'related_degrees':
degrees_ids = rule.salary_amount_ids.filtered(
lambda item: item.salary_scale_degree.id == contract.salary_degree.id)
if degrees_ids:
for d in degrees_ids:
try:
return float(d.salary * total_percent / 100)
except:
raise UserError(
_('Wrong quantity defined for salary rule %s (%s).') % (
rule.name, rule.code))
else:
return 0
else:
try:
return 0
except:
raise UserError(_('There is no total for rule : %s') % (rule.name))
elif rule.amount_select == 'fix':
return rule._compute_rule(localdict)[0]
else:
return rule._compute_rule(localdict)[0]
# change state to draft and cancel account move if it's state draft
def re_draft(self):
if self.account_move_id:
if self.account_move_id.state == 'draft':
self.account_move_id.unlink()
self.account_move_id = False
else:
raise UserError(_(
'You can not re-draft reconcile leaves because account move with ID "%s" in state Posted') % self.account_move_id.name)
for item in self.loans_ids:
last_date = fields.Datetime.from_string(self.write_date).date().month
for install in item.deduction_lines:
loan_date = fields.Datetime.from_string(install.write_date).date().month
if loan_date >= last_date and install.paid == True:
install.paid = False
item.state = 'pay'
self.state = 'draft'
# change state to submit
def submit(self):
self.constain_holiday_request()
self.state = 'submit'
# change state to direct_manager
def direct_manager(self):
self.state = 'direct_manager'
# change state to hr_manager
def hr_manager(self):
self.state = 'hr_manager'
# change state to finance_manager
def finance_manager(self):
self.state = 'finance_manager'
# change state to gm_manager
def gm_manager(self):
self.state = 'gm_manager'
# change state to create_journal_entry
def pay(self):
line_vals = []
if self.allowance_deduction:
for item in self.allowance_deduction:
# check if amount greater than 0.0 to fill move account lines
if item.amount > 0.0:
# check for deduction credit account
if item.salary_rule_id.category_id.rule_type == 'deduction':
if not item.salary_rule_id.rule_credit_account_id:
raise UserError(_('Undefined credit account for salary rule %s (%s).') % (
item.salary_rule_id.name, item.salary_rule_id.code))
# check for allowance debit account
elif item.salary_rule_id.category_id.rule_type == 'allowance':
if not item.salary_rule_id.rule_debit_account_id:
raise UserError(_('Undefined credit account for salary rule %s (%s).') % (
item.salary_rule_id.name, item.salary_rule_id.code))
else:
if not item.salary_rule_id.rule_credit_account_id:
raise UserError(_(
'Check account debit for salary rule "%s" ') % item.salary_rule_id.name)
# fill move lines with allowance deduction
if item.salary_rule_id.category_id.rule_type == 'allowance':
line_vals.append({
'name': 'Employee %s allowance.' % (self.employee_id.name),
'debit': abs(item.amount),
'account_id': item.salary_rule_id.rule_debit_account_id.id,
'partner_id': self.employee_id.user_id.partner_id.id})
elif item.salary_rule_id.category_id.rule_type == 'deduction':
line_vals.append({
'name': 'Employee %s deduction.' % (self.employee_id.name),
'credit': abs(item.amount),
'account_id': item.salary_rule_id.rule_credit_account_id.id,
'partner_id': self.employee_id.user_id.partner_id.id})
else:
line_vals.append({
'name': 'Employee %s rule.' % (self.employee_id.name),
'debit': abs(item.amount),
'account_id': item.salary_rule_id.rule_debit_account_id.id,
'partner_id': self.employee_id.user_id.partner_id.id})
for item in self.loans_ids:
line_vals.append({
'name': 'Employee %s loan.' % (self.employee_id.name),
'credit': abs(item.remaining_loan_amount),
'account_id': item.request_type.account_id.id,
'partner_id': self.employee_id.user_id.partner_id.id})
for install in item.deduction_lines:
if install.paid == False:
install.paid = True
item.state = 'closed'
line_vals.append({
'name': 'Employee %s Net.' % (self.employee_id.name),
'credit': abs(self.net),
'account_id': self.journal_id.default_account_id.id,
'partner_id': self.employee_id.user_id.partner_id.id})
# Constrain on net that must be greater than zero
if self.net < 0.0:
raise UserError(_('The Net must be not negative value .'))
move = self.env['account.move'].create({
'state': 'draft',
'journal_id': self.journal_id.id,
'date': self.date,
'ref': 'Reconcile leave of "%s" ' % self.employee_id.name,
'line_ids': [(0, 0, value) for value in line_vals]
})
self.write({'account_move_id': move.id})
self.state = 'pay'
# change state to refuse
def refuse(self):
self.state = 'refuse'
# Override create function
@api.model
def create(self, values):
result = super(ReconcileLeaves, self).create(values)
if result.net < 0.0:
raise UserError(_('The Net must be not negative value .'))
return result
class ReconcileAllowanceDeduction(models.Model):
_name = 'reconcile.allowance.deduction'
_description = 'Reconcile Allowance Deduction'
amount = fields.Float()
# relational fields
reconcile_id = fields.Many2one('reconcile.leaves')
salary_rule_id = fields.Many2one('hr.salary.rule')
category_id = fields.Many2one('hr.salary.rule.category', related='salary_rule_id.category_id')
class ReconcileAllowanceJournal(models.Model):
_name = 'reconcile.allowance.journal'
_description = 'Reconcile Allowance Journal'
amount = fields.Float()
# relational fields
journal_entry_ids_lines = fields.Many2one('reconcile.leaves')
journal_id = fields.Many2one('account.journal')