odoo18/addons_extensions/weekly_timesheets/models/weekly_period_timesheets.py

489 lines
14 KiB
Python

from odoo import api, fields, models, _
from datetime import timedelta
from odoo.exceptions import ValidationError, UserError
class WeeklyPeriodTimesheets(models.Model):
_name = 'weekly.periodtimesheets'
_description = 'Weekly Period Timesheets'
_rec_name = 'employee_id'
employee_id = fields.Many2one(
'hr.employee',
string="Employee",
required=True,
)
year_id = fields.Many2one(
'week.timesheet',
string="Year",
required=True,
)
week_line_id = fields.Many2one(
'week.timesheet.line',
string="Week",
required=True,
domain="[('week_timesheet_id', '=', year_id)]"
)
timesheet_line_ids = fields.One2many(
'weekly.periodtimesheets.line',
'weekly_timesheet_id',
string="Timesheet Lines"
)
total_hours = fields.Float(
string="Total Hours",
compute="_compute_total_hours",
store=True
)
@api.depends('timesheet_line_ids.hours')
def _compute_total_hours(self):
for rec in self:
rec.total_hours = sum(
rec.timesheet_line_ids.mapped('hours')
)
state = fields.Selection([
('draft', 'Draft'),
('submitted', 'Submitted'),
('approved', 'Approved'),
('rejected', 'Rejected'),
], string="Status", default='draft')
def action_submit(self):
for rec in self:
if rec.total_hours < 40:
raise ValidationError(
"Weekly timesheet hours cannot be less than 40 hours."
)
analytic_lines = self.env[
'account.analytic.line'
].search([
('employee_id', '=', rec.employee_id.id),
('date', '>=', rec.week_line_id.date_from),
('date', '<=', rec.week_line_id.date_to),
])
for line in analytic_lines:
vals = {
'weekly_submitted': True
}
if not line.pm_approval_required:
vals['approval_state'] = 'approved'
line.write(vals)
rec.state = 'submitted'
template = self.env.ref(
'weekly_timesheets.email_template_weekly_timesheet_submit'
)
if template:
template.send_mail(
rec.id,
force_send=True
)
def action_reject(self):
for rec in self:
analytic_lines = self.env[
'account.analytic.line'
].search([
('employee_id', '=', rec.employee_id.id),
('date', '>=', rec.week_line_id.date_from),
('date', '<=', rec.week_line_id.date_to),
])
for line in analytic_lines:
vals = {
'weekly_submitted': True
}
if not line.pm_approval_required:
vals['approval_state'] = 'rejected'
line.write(vals)
rec.state = 'rejected'
template = self.env.ref(
'weekly_timesheets.email_template_weekly_timesheet_reject'
)
if template:
template.send_mail(
rec.id,
force_send=True
)
def action_approve(self):
for rec in self:
if rec.state != 'submitted':
raise UserError(
_("Only submitted timesheets can be approved.")
)
# Employee Manager
employee_manager = (
rec.employee_id.parent_id.user_id
)
# Project Administrator
is_admin = self.env.user.has_group(
'project.group_project_manager'
)
# Validation
if (
self.env.user != employee_manager
and not is_admin
):
raise UserError(
_("Only Employee Manager or Project Administrator can approve.")
)
# Ensure all PM approvals completed
pending_lines = self.env[
'account.analytic.line'
].search([
('employee_id', '=', rec.employee_id.id),
('date', '>=', rec.week_line_id.date_from),
('date', '<=', rec.week_line_id.date_to),
('pm_approval_required', '=', True),
('approval_state', '!=', 'approved'),
])
if pending_lines:
raise UserError(
_("All Project Manager approvals must be completed.")
)
rec.state = 'approved'
template = self.env.ref(
'weekly_timesheets.email_template_weekly_timesheet_approve'
)
if template:
template.send_mail(
rec.id,
force_send=True
)
def action_reset_to_draft(self):
for rec in self:
analytic_lines = self.env[
'account.analytic.line'
].search([
('employee_id', '=', rec.employee_id.id),
('date', '>=', rec.week_line_id.date_from),
('date', '<=', rec.week_line_id.date_to),
])
analytic_lines.write({
'weekly_submitted': False,
})
rec.state = 'draft'
def action_refresh_timesheets(self):
for rec in self:
rec.timesheet_line_ids = [(5, 0, 0)]
if not rec.employee_id or not rec.week_line_id:
return
lines = []
current_date = rec.week_line_id.date_from
while current_date <= rec.week_line_id.date_to:
analytic_lines = self.env[
'account.analytic.line'
].search([
('employee_id', '=', rec.employee_id.id),
('date', '=', current_date),
])
total_hours = sum(
analytic_lines.mapped('unit_amount')
)
lines.append((0, 0, {
'date': current_date,
'day_name': current_date.strftime("%A"),
'hours': total_hours,
}))
current_date += timedelta(days=1)
rec.timesheet_line_ids = lines
@api.onchange('employee_id', 'week_line_id')
def _onchange_employee_week(self):
self.timesheet_line_ids = [(5, 0, 0)]
if not self.employee_id or not self.week_line_id:
return
lines = []
current_date = self.week_line_id.date_from
while current_date <= self.week_line_id.date_to:
analytic_lines = self.env[
'account.analytic.line'
].search([
('employee_id', '=', self.employee_id.id),
('date', '=', current_date),
])
total_hours = sum(
analytic_lines.mapped('unit_amount')
)
lines.append((0, 0, {
'date': current_date,
'day_name': current_date.strftime("%A"),
'hours': total_hours,
}))
current_date += timedelta(days=1)
self.timesheet_line_ids = lines
self.action_refresh_timesheets()
analytic_line_ids = fields.One2many(
'account.analytic.line',
compute='_compute_analytic_lines',
inverse='_inverse_analytic_lines',
string="Original Timesheets"
)
def _inverse_analytic_lines(self):
pass
@api.depends('employee_id', 'week_line_id')
def _compute_analytic_lines(self):
for rec in self:
rec.analytic_line_ids = False
if not rec.employee_id or not rec.week_line_id:
continue
analytic_lines = self.env[
'account.analytic.line'
].search([
('employee_id', '=', rec.employee_id.id),
('date', '>=', rec.week_line_id.date_from),
('date', '<=', rec.week_line_id.date_to),
])
rec.analytic_line_ids = analytic_lines
all_pm_approved = fields.Boolean(
compute='_compute_all_pm_approved'
)
def _compute_all_pm_approved(self):
for rec in self:
pending_lines = self.env[
'account.analytic.line'
].search([
('employee_id', '=', rec.employee_id.id),
('date', '>=', rec.week_line_id.date_from),
('date', '<=', rec.week_line_id.date_to),
('pm_approval_required', '=', True),
('approval_state', '!=', 'approved'),
])
rec.all_pm_approved = not pending_lines
class WeeklyPeriodTimesheetsLine(models.Model):
_name = 'weekly.periodtimesheets.line'
_description = 'Weekly Period Timesheet Line'
weekly_timesheet_id = fields.Many2one(
'weekly.periodtimesheets',
string="Weekly Timesheet"
)
date = fields.Date(
string="Date"
)
day_name = fields.Char(
string="Day"
)
hours = fields.Float(
string="Hours"
)
class AccountAnalyticLine(models.Model):
_inherit = 'account.analytic.line'
approval_state = fields.Selection([
('draft', 'Draft'),
('approved', 'Approved'),
('rejected', 'Rejected'),
], string="Approval Status", default='draft')
weekly_submitted = fields.Boolean(
string="Weekly Submitted",
default=False
)
pm_approval_required = fields.Boolean(
compute='_compute_pm_approval_required',
store=True
)
@api.depends(
'task_id.is_generic',
'project_id.privacy_visibility'
)
def _compute_pm_approval_required(self):
for rec in self:
rec.pm_approval_required = not (
rec.task_id.is_generic
or rec.project_id.privacy_visibility != 'followers'
)
def action_pm_approve(self):
for rec in self:
weekly_sheet = self.env[
'weekly.periodtimesheets'
].search([
('employee_id', '=', rec.employee_id.id),
('week_line_id.date_from', '<=', rec.date),
('week_line_id.date_to', '>=', rec.date),
], limit=1)
# Final approval completed
if weekly_sheet.state == 'approved':
raise UserError(
_("Weekly Timesheet already approved.")
)
# Weekly not submitted
if not rec.weekly_submitted:
raise UserError(
_("Weekly Timesheet is not submitted yet.")
)
is_project_manager = (
rec.project_id.user_id == self.env.user
)
is_admin = self.env.user.has_group(
'project.group_project_manager'
)
if not (is_project_manager or is_admin):
raise UserError(
_("Only Project Manager or Administrator can approve.")
)
rec.approval_state = 'approved'
def action_pm_reject(self):
for rec in self:
weekly_sheet = self.env[
'weekly.periodtimesheets'
].search([
('employee_id', '=', rec.employee_id.id),
('week_line_id.date_from', '<=', rec.date),
('week_line_id.date_to', '>=', rec.date),
], limit=1)
if weekly_sheet.state == 'approved':
raise UserError(
_("Weekly Timesheet already approved.")
)
if not rec.weekly_submitted:
raise UserError(
_("Weekly Timesheet is not submitted yet.")
)
is_project_manager = (
rec.project_id.user_id == self.env.user
)
is_admin = self.env.user.has_group(
'project.group_project_manager'
)
if not (is_project_manager or is_admin):
raise UserError(
_("Only Project Manager or Administrator can reject.")
)
rec.approval_state = 'rejected'
def _check_weekly_submission(self):
for rec in self:
if not rec.employee_id or not rec.date:
continue
weekly_sheet = self.env[
'weekly.periodtimesheets'
].search([
('employee_id', '=', rec.employee_id.id),
('state', '=', 'submitted'),
('week_line_id.date_from', '<=', rec.date),
('week_line_id.date_to', '>=', rec.date),
], limit=1)
if weekly_sheet:
raise ValidationError(
"Weekly Timesheet already submitted. "
"You cannot create, edit or delete "
"timesheets for this week."
)
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
records._check_weekly_submission()
return records
def write(self, vals):
# Skip validation for system approval updates
skip_fields = [
'approval_state',
'weekly_submitted',
]
if not all(field in skip_fields for field in vals.keys()):
self._check_weekly_submission()
return super().write(vals)
def unlink(self):
self._check_weekly_submission()
return super().unlink()