feat(fusion_clock): open shifts + self-assign + bulk apply [B4-B5]
Model: fclk_create_open_shifts/claim_open_shift/release_shift (days-before cutoff + role eligibility)/bulk_apply. Planner: Open Shift… panel, open-shifts strip with delete, Apply-to-dept; load includes open shifts. Portal: claim open shifts + release own upcoming shifts with feedback banners. Tests for claim/role-gate/release/bulk. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,8 +9,10 @@
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from datetime import timedelta
|
||||
from urllib.parse import quote
|
||||
|
||||
from odoo import http, fields
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -30,6 +32,7 @@ class FusionClockSchedulePortal(http.Controller):
|
||||
horizon_local = today_local + timedelta(days=60)
|
||||
|
||||
Schedule = request.env['fusion.clock.schedule'].sudo()
|
||||
cutoff = employee.company_id.fclk_self_unassign_days_before or 0
|
||||
entries = []
|
||||
for sch in Schedule.search([
|
||||
('employee_id', '=', employee.id),
|
||||
@@ -54,11 +57,37 @@ class FusionClockSchedulePortal(http.Controller):
|
||||
'role_name': sch.role_id.name if sch.role_id else '',
|
||||
'role_color': sch.role_id._get_color_from_code() if sch.role_id else '',
|
||||
'note': sch.note or '',
|
||||
'schedule_id': sch.id,
|
||||
'releasable': (day - today_local).days >= cutoff,
|
||||
},
|
||||
))
|
||||
|
||||
entries.sort(key=lambda e: e[0])
|
||||
|
||||
# Open shifts the employee may claim: company-scoped, future, and either
|
||||
# role-eligible (allowed-role list contains the shift role) or roleless.
|
||||
open_shifts = []
|
||||
for row in Schedule.search([
|
||||
('is_open', '=', True),
|
||||
('state', '=', 'posted'),
|
||||
('company_id', '=', employee.company_id.id),
|
||||
('schedule_date', '>=', today_local),
|
||||
('schedule_date', '<=', horizon_local),
|
||||
], order='schedule_date asc, start_time asc', limit=100):
|
||||
if row.role_id and employee.x_fclk_role_ids and row.role_id not in employee.x_fclk_role_ids:
|
||||
continue
|
||||
d = row.schedule_date
|
||||
open_shifts.append({
|
||||
'id': row.id,
|
||||
'date_full': d.strftime('%a, %b %d'),
|
||||
'time_range': '%s - %s' % (
|
||||
Schedule.fclk_float_to_display(row.start_time),
|
||||
Schedule.fclk_float_to_display(row.end_time),
|
||||
),
|
||||
'role_name': row.role_id.name if row.role_id else '',
|
||||
'duration_hours': round(row.planned_hours or 0.0, 1),
|
||||
})
|
||||
|
||||
groups = OrderedDict()
|
||||
for _key, day, item in entries:
|
||||
delta_days = (day - today_local).days
|
||||
@@ -86,7 +115,40 @@ class FusionClockSchedulePortal(http.Controller):
|
||||
'groups': groups,
|
||||
'slot_count': len(entries),
|
||||
'next_slot': next_slot_data,
|
||||
'open_shifts': open_shifts,
|
||||
'error': kw.get('err'),
|
||||
'success': kw.get('ok'),
|
||||
'page_name': 'fusion_clock_schedule',
|
||||
'show_payslips': 'hr.payslip' in request.env,
|
||||
}
|
||||
return request.render('fusion_clock.portal_schedule_page', values)
|
||||
|
||||
@http.route('/my/clock/schedule/claim', type='http', auth='user',
|
||||
methods=['POST'], website=True)
|
||||
def claim_open_shift(self, schedule_id=None, **kw):
|
||||
employee = request.env.user.employee_id
|
||||
if not employee or not schedule_id:
|
||||
return request.redirect('/my/clock/schedule')
|
||||
Schedule = request.env['fusion.clock.schedule'].sudo()
|
||||
sch = Schedule.browse(int(schedule_id))
|
||||
try:
|
||||
Schedule.fclk_claim_open_shift(sch, employee)
|
||||
return request.redirect('/my/clock/schedule?ok=claimed')
|
||||
except ValidationError as exc:
|
||||
return request.redirect(
|
||||
'/my/clock/schedule?err=' + quote(str(exc.args[0] if exc.args else exc)))
|
||||
|
||||
@http.route('/my/clock/schedule/release', type='http', auth='user',
|
||||
methods=['POST'], website=True)
|
||||
def release_shift(self, schedule_id=None, **kw):
|
||||
employee = request.env.user.employee_id
|
||||
if not employee or not schedule_id:
|
||||
return request.redirect('/my/clock/schedule')
|
||||
Schedule = request.env['fusion.clock.schedule'].sudo()
|
||||
sch = Schedule.browse(int(schedule_id))
|
||||
try:
|
||||
Schedule.fclk_release_shift(sch, employee)
|
||||
return request.redirect('/my/clock/schedule?ok=released')
|
||||
except ValidationError as exc:
|
||||
return request.redirect(
|
||||
'/my/clock/schedule?err=' + quote(str(exc.args[0] if exc.args else exc)))
|
||||
|
||||
Reference in New Issue
Block a user