2018-01-16 06:58:15 +01:00
# -*- coding: utf-8 -*-
from datetime import date , datetime , timedelta
2018-01-16 11:34:37 +01:00
from flectra import api , fields , models , SUPERUSER_ID , _
from flectra . exceptions import UserError
from flectra . tools import DEFAULT_SERVER_DATE_FORMAT , DEFAULT_SERVER_DATETIME_FORMAT
2018-01-16 06:58:15 +01:00
class MaintenanceStage ( models . Model ) :
""" Model for case stages. This models the main stages of a Maintenance Request management flow. """
_name = ' maintenance.stage '
_description = ' Maintenance Stage '
_order = ' sequence, id '
name = fields . Char ( ' Name ' , required = True , translate = True )
sequence = fields . Integer ( ' Sequence ' , default = 20 )
fold = fields . Boolean ( ' Folded in Maintenance Pipe ' )
done = fields . Boolean ( ' Request Done ' )
class MaintenanceEquipmentCategory ( models . Model ) :
_name = ' maintenance.equipment.category '
_inherit = [ ' mail.alias.mixin ' , ' mail.thread ' ]
_description = ' Asset Category '
@api.one
@api.depends ( ' equipment_ids ' )
def _compute_fold ( self ) :
self . fold = False if self . equipment_count else True
name = fields . Char ( ' Category Name ' , required = True , translate = True )
technician_user_id = fields . Many2one ( ' res.users ' , ' Responsible ' , track_visibility = ' onchange ' , default = lambda self : self . env . uid , oldname = ' user_id ' )
color = fields . Integer ( ' Color Index ' )
note = fields . Text ( ' Comments ' , translate = True )
equipment_ids = fields . One2many ( ' maintenance.equipment ' , ' category_id ' , string = ' Equipments ' , copy = False )
equipment_count = fields . Integer ( string = " Equipment " , compute = ' _compute_equipment_count ' )
maintenance_ids = fields . One2many ( ' maintenance.request ' , ' category_id ' , copy = False )
maintenance_count = fields . Integer ( string = " Maintenance " , compute = ' _compute_maintenance_count ' )
alias_id = fields . Many2one (
' mail.alias ' , ' Alias ' , ondelete = ' restrict ' , required = True ,
help = " Email alias for this equipment category. New emails will automatically "
" create new maintenance request for this equipment category. " )
fold = fields . Boolean ( string = ' Folded in Maintenance Pipe ' , compute = ' _compute_fold ' , store = True )
@api.multi
def _compute_equipment_count ( self ) :
equipment_data = self . env [ ' maintenance.equipment ' ] . read_group ( [ ( ' category_id ' , ' in ' , self . ids ) ] , [ ' category_id ' ] , [ ' category_id ' ] )
mapped_data = dict ( [ ( m [ ' category_id ' ] [ 0 ] , m [ ' category_id_count ' ] ) for m in equipment_data ] )
for category in self :
category . equipment_count = mapped_data . get ( category . id , 0 )
@api.multi
def _compute_maintenance_count ( self ) :
maintenance_data = self . env [ ' maintenance.request ' ] . read_group ( [ ( ' category_id ' , ' in ' , self . ids ) ] , [ ' category_id ' ] , [ ' category_id ' ] )
mapped_data = dict ( [ ( m [ ' category_id ' ] [ 0 ] , m [ ' category_id_count ' ] ) for m in maintenance_data ] )
for category in self :
category . maintenance_count = mapped_data . get ( category . id , 0 )
@api.model
def create ( self , vals ) :
self = self . with_context ( alias_model_name = ' maintenance.request ' , alias_parent_model_name = self . _name )
if not vals . get ( ' alias_name ' ) :
vals [ ' alias_name ' ] = vals . get ( ' name ' )
category_id = super ( MaintenanceEquipmentCategory , self ) . create ( vals )
category_id . alias_id . write ( { ' alias_parent_thread_id ' : category_id . id , ' alias_defaults ' : { ' category_id ' : category_id . id } } )
return category_id
@api.multi
def unlink ( self ) :
MailAlias = self . env [ ' mail.alias ' ]
for category in self :
if category . equipment_ids or category . maintenance_ids :
raise UserError ( _ ( " You cannot delete an equipment category containing equipments or maintenance requests. " ) )
MailAlias + = category . alias_id
res = super ( MaintenanceEquipmentCategory , self ) . unlink ( )
MailAlias . unlink ( )
return res
def get_alias_model_name ( self , vals ) :
return vals . get ( ' alias_model ' , ' maintenance.equipment ' )
def get_alias_values ( self ) :
values = super ( MaintenanceEquipmentCategory , self ) . get_alias_values ( )
values [ ' alias_defaults ' ] = { ' category_id ' : self . id }
return values
class MaintenanceEquipment ( models . Model ) :
_name = ' maintenance.equipment '
_inherit = [ ' mail.thread ' , ' mail.activity.mixin ' ]
_description = ' Equipment '
@api.multi
def _track_subtype ( self , init_values ) :
self . ensure_one ( )
if ' owner_user_id ' in init_values and self . owner_user_id :
return ' maintenance.mt_mat_assign '
return super ( MaintenanceEquipment , self ) . _track_subtype ( init_values )
@api.multi
def name_get ( self ) :
result = [ ]
for record in self :
if record . name and record . serial_no :
result . append ( ( record . id , record . name + ' / ' + record . serial_no ) )
if record . name and not record . serial_no :
result . append ( ( record . id , record . name ) )
return result
@api.model
def name_search ( self , name , args = None , operator = ' ilike ' , limit = 100 ) :
args = args or [ ]
recs = self . browse ( )
if name :
recs = self . search ( [ ( ' name ' , ' = ' , name ) ] + args , limit = limit )
if not recs :
recs = self . search ( [ ( ' name ' , operator , name ) ] + args , limit = limit )
return recs . name_get ( )
name = fields . Char ( ' Equipment Name ' , required = True , translate = True )
active = fields . Boolean ( default = True )
technician_user_id = fields . Many2one ( ' res.users ' , string = ' Technician ' , track_visibility = ' onchange ' , oldname = ' user_id ' )
owner_user_id = fields . Many2one ( ' res.users ' , string = ' Owner ' , track_visibility = ' onchange ' )
category_id = fields . Many2one ( ' maintenance.equipment.category ' , string = ' Equipment Category ' ,
track_visibility = ' onchange ' , group_expand = ' _read_group_category_ids ' )
partner_id = fields . Many2one ( ' res.partner ' , string = ' Vendor ' , domain = " [( ' supplier ' , ' = ' , 1)] " )
partner_ref = fields . Char ( ' Vendor Reference ' )
location = fields . Char ( ' Location ' )
model = fields . Char ( ' Model ' )
serial_no = fields . Char ( ' Serial Number ' , copy = False )
assign_date = fields . Date ( ' Assigned Date ' , track_visibility = ' onchange ' )
cost = fields . Float ( ' Cost ' )
note = fields . Text ( ' Note ' )
warranty = fields . Date ( ' Warranty ' )
color = fields . Integer ( ' Color Index ' )
scrap_date = fields . Date ( ' Scrap Date ' )
maintenance_ids = fields . One2many ( ' maintenance.request ' , ' equipment_id ' )
maintenance_count = fields . Integer ( compute = ' _compute_maintenance_count ' , string = " Maintenance " , store = True )
maintenance_open_count = fields . Integer ( compute = ' _compute_maintenance_count ' , string = " Current Maintenance " , store = True )
period = fields . Integer ( ' Days between each preventive maintenance ' )
next_action_date = fields . Date ( compute = ' _compute_next_maintenance ' , string = ' Date of the next preventive maintenance ' , store = True )
maintenance_team_id = fields . Many2one ( ' maintenance.team ' , string = ' Maintenance Team ' )
maintenance_duration = fields . Float ( help = " Maintenance Duration in hours. " )
@api.depends ( ' period ' , ' maintenance_ids.request_date ' , ' maintenance_ids.close_date ' )
def _compute_next_maintenance ( self ) :
date_now = fields . Date . context_today ( self )
for equipment in self . filtered ( lambda x : x . period > 0 ) :
next_maintenance_todo = self . env [ ' maintenance.request ' ] . search ( [
( ' equipment_id ' , ' = ' , equipment . id ) ,
( ' maintenance_type ' , ' = ' , ' preventive ' ) ,
( ' stage_id.done ' , ' != ' , True ) ,
( ' close_date ' , ' = ' , False ) ] , order = " request_date asc " , limit = 1 )
last_maintenance_done = self . env [ ' maintenance.request ' ] . search ( [
( ' equipment_id ' , ' = ' , equipment . id ) ,
( ' maintenance_type ' , ' = ' , ' preventive ' ) ,
( ' stage_id.done ' , ' = ' , True ) ,
( ' close_date ' , ' != ' , False ) ] , order = " close_date desc " , limit = 1 )
if next_maintenance_todo and last_maintenance_done :
next_date = next_maintenance_todo . request_date
date_gap = fields . Date . from_string ( next_maintenance_todo . request_date ) - fields . Date . from_string ( last_maintenance_done . close_date )
# If the gap between the last_maintenance_done and the next_maintenance_todo one is bigger than 2 times the period and next request is in the future
# We use 2 times the period to avoid creation too closed request from a manually one created
if date_gap > timedelta ( 0 ) and date_gap > timedelta ( days = equipment . period ) * 2 and fields . Date . from_string ( next_maintenance_todo . request_date ) > fields . Date . from_string ( date_now ) :
# If the new date still in the past, we set it for today
if fields . Date . from_string ( last_maintenance_done . close_date ) + timedelta ( days = equipment . period ) < fields . Date . from_string ( date_now ) :
next_date = date_now
else :
next_date = fields . Date . to_string ( fields . Date . from_string ( last_maintenance_done . close_date ) + timedelta ( days = equipment . period ) )
elif next_maintenance_todo :
next_date = next_maintenance_todo . request_date
date_gap = fields . Date . from_string ( next_maintenance_todo . request_date ) - fields . Date . from_string ( date_now )
# If next maintenance to do is in the future, and in more than 2 times the period, we insert an new request
# We use 2 times the period to avoid creation too closed request from a manually one created
if date_gap > timedelta ( 0 ) and date_gap > timedelta ( days = equipment . period ) * 2 :
next_date = fields . Date . to_string ( fields . Date . from_string ( date_now ) + timedelta ( days = equipment . period ) )
elif last_maintenance_done :
next_date = fields . Date . from_string ( last_maintenance_done . close_date ) + timedelta ( days = equipment . period )
# If when we add the period to the last maintenance done and we still in past, we plan it for today
if next_date < fields . Date . from_string ( date_now ) :
next_date = date_now
else :
next_date = fields . Date . to_string ( fields . Date . from_string ( date_now ) + timedelta ( days = equipment . period ) )
equipment . next_action_date = next_date
@api.one
@api.depends ( ' maintenance_ids.stage_id.done ' )
def _compute_maintenance_count ( self ) :
self . maintenance_count = len ( self . maintenance_ids )
self . maintenance_open_count = len ( self . maintenance_ids . filtered ( lambda x : not x . stage_id . done ) )
@api.onchange ( ' category_id ' )
def _onchange_category_id ( self ) :
self . technician_user_id = self . category_id . technician_user_id
_sql_constraints = [
( ' serial_no ' , ' unique(serial_no) ' , " Another asset already exists with this serial number! " ) ,
]
@api.model
def create ( self , vals ) :
equipment = super ( MaintenanceEquipment , self ) . create ( vals )
if equipment . owner_user_id :
equipment . message_subscribe_users ( user_ids = [ equipment . owner_user_id . id ] )
return equipment
@api.multi
def write ( self , vals ) :
if vals . get ( ' owner_user_id ' ) :
self . message_subscribe_users ( user_ids = [ vals [ ' owner_user_id ' ] ] )
return super ( MaintenanceEquipment , self ) . write ( vals )
2018-04-05 10:25:40 +02:00
@api.model
def _message_get_auto_subscribe_fields ( self , updated_fields , auto_follow_fields = None ) :
""" mail.thread override so user_id which has no special access allowance is not
automatically subscribed .
"""
if auto_follow_fields is None :
auto_follow_fields = [ ]
return super ( MaintenanceEquipment , self ) . _message_get_auto_subscribe_fields ( updated_fields , auto_follow_fields )
2018-01-16 06:58:15 +01:00
@api.model
def _read_group_category_ids ( self , categories , domain , order ) :
""" Read group customization in order to display all the categories in
the kanban view , even if they are empty .
"""
category_ids = categories . _search ( [ ] , order = order , access_rights_uid = SUPERUSER_ID )
return categories . browse ( category_ids )
def _create_new_request ( self , date ) :
self . ensure_one ( )
self . env [ ' maintenance.request ' ] . create ( {
' name ' : _ ( ' Preventive Maintenance - %s ' ) % self . name ,
' request_date ' : date ,
' schedule_date ' : date ,
' category_id ' : self . category_id . id ,
' equipment_id ' : self . id ,
' maintenance_type ' : ' preventive ' ,
' owner_user_id ' : self . owner_user_id . id ,
' technician_user_id ' : self . technician_user_id . id ,
' maintenance_team_id ' : self . maintenance_team_id . id ,
' duration ' : self . maintenance_duration ,
} )
@api.model
def _cron_generate_requests ( self ) :
"""
Generates maintenance request on the next_action_date or today if none exists
"""
for equipment in self . search ( [ ( ' period ' , ' > ' , 0 ) ] ) :
next_requests = self . env [ ' maintenance.request ' ] . search ( [ ( ' stage_id.done ' , ' = ' , False ) ,
( ' equipment_id ' , ' = ' , equipment . id ) ,
( ' maintenance_type ' , ' = ' , ' preventive ' ) ,
( ' request_date ' , ' = ' , equipment . next_action_date ) ] )
if not next_requests :
equipment . _create_new_request ( equipment . next_action_date )
class MaintenanceRequest ( models . Model ) :
_name = ' maintenance.request '
_inherit = [ ' mail.thread ' , ' mail.activity.mixin ' ]
_description = ' Maintenance Requests '
_order = " id desc "
@api.returns ( ' self ' )
def _default_stage ( self ) :
return self . env [ ' maintenance.stage ' ] . search ( [ ] , limit = 1 )
@api.multi
def _track_subtype ( self , init_values ) :
self . ensure_one ( )
if ' stage_id ' in init_values and self . stage_id . sequence < = 1 :
return ' maintenance.mt_req_created '
elif ' stage_id ' in init_values and self . stage_id . sequence > 1 :
return ' maintenance.mt_req_status '
return super ( MaintenanceRequest , self ) . _track_subtype ( init_values )
def _get_default_team_id ( self ) :
return self . env . ref ( ' maintenance.equipment_team_maintenance ' , raise_if_not_found = False )
name = fields . Char ( ' Subjects ' , required = True )
description = fields . Text ( ' Description ' )
request_date = fields . Date ( ' Request Date ' , track_visibility = ' onchange ' , default = fields . Date . context_today ,
help = " Date requested for the maintenance to happen " )
owner_user_id = fields . Many2one ( ' res.users ' , string = ' Created by ' , default = lambda s : s . env . uid )
category_id = fields . Many2one ( ' maintenance.equipment.category ' , related = ' equipment_id.category_id ' , string = ' Category ' , store = True , readonly = True )
equipment_id = fields . Many2one ( ' maintenance.equipment ' , string = ' Equipment ' , index = True )
technician_user_id = fields . Many2one ( ' res.users ' , string = ' Owner ' , track_visibility = ' onchange ' , oldname = ' user_id ' )
stage_id = fields . Many2one ( ' maintenance.stage ' , string = ' Stage ' , track_visibility = ' onchange ' ,
group_expand = ' _read_group_stage_ids ' , default = _default_stage )
priority = fields . Selection ( [ ( ' 0 ' , ' Very Low ' ) , ( ' 1 ' , ' Low ' ) , ( ' 2 ' , ' Normal ' ) , ( ' 3 ' , ' High ' ) ] , string = ' Priority ' )
color = fields . Integer ( ' Color Index ' )
close_date = fields . Date ( ' Close Date ' , help = " Date the maintenance was finished. " )
kanban_state = fields . Selection ( [ ( ' normal ' , ' In Progress ' ) , ( ' blocked ' , ' Blocked ' ) , ( ' done ' , ' Ready for next stage ' ) ] ,
string = ' Kanban State ' , required = True , default = ' normal ' , track_visibility = ' onchange ' )
# active = fields.Boolean(default=True, help="Set active to false to hide the maintenance request without deleting it.")
archive = fields . Boolean ( default = False , help = " Set archive to true to hide the maintenance request without deleting it. " )
maintenance_type = fields . Selection ( [ ( ' corrective ' , ' Corrective ' ) , ( ' preventive ' , ' Preventive ' ) ] , string = ' Maintenance Type ' , default = " corrective " )
schedule_date = fields . Datetime ( ' Scheduled Date ' , help = " Date the maintenance team plans the maintenance. It should not differ much from the Request Date. " )
maintenance_team_id = fields . Many2one ( ' maintenance.team ' , string = ' Team ' , required = True , default = _get_default_team_id )
duration = fields . Float ( help = " Duration in minutes and seconds. " )
@api.multi
def archive_equipment_request ( self ) :
self . write ( { ' archive ' : True } )
@api.multi
def reset_equipment_request ( self ) :
""" Reinsert the maintenance request into the maintenance pipe in the first stage """
first_stage_obj = self . env [ ' maintenance.stage ' ] . search ( [ ] , order = " sequence asc " , limit = 1 )
# self.write({'active': True, 'stage_id': first_stage_obj.id})
self . write ( { ' archive ' : False , ' stage_id ' : first_stage_obj . id } )
@api.onchange ( ' equipment_id ' )
def onchange_equipment_id ( self ) :
if self . equipment_id :
self . technician_user_id = self . equipment_id . technician_user_id if self . equipment_id . technician_user_id else self . equipment_id . category_id . technician_user_id
self . category_id = self . equipment_id . category_id
if self . equipment_id . maintenance_team_id :
self . maintenance_team_id = self . equipment_id . maintenance_team_id . id
@api.onchange ( ' category_id ' )
def onchange_category_id ( self ) :
if not self . technician_user_id or not self . equipment_id or ( self . technician_user_id and not self . equipment_id . technician_user_id ) :
self . technician_user_id = self . category_id . technician_user_id
@api.model
def create ( self , vals ) :
# context: no_log, because subtype already handle this
self = self . with_context ( mail_create_nolog = True )
request = super ( MaintenanceRequest , self ) . create ( vals )
if request . owner_user_id or request . technician_user_id :
request . _add_followers ( )
if request . equipment_id and not request . maintenance_team_id :
request . maintenance_team_id = request . equipment_id . maintenance_team_id
return request
@api.multi
def write ( self , vals ) :
# Overridden to reset the kanban_state to normal whenever
# the stage (stage_id) of the Maintenance Request changes.
if vals and ' kanban_state ' not in vals and ' stage_id ' in vals :
vals [ ' kanban_state ' ] = ' normal '
res = super ( MaintenanceRequest , self ) . write ( vals )
if vals . get ( ' owner_user_id ' ) or vals . get ( ' technician_user_id ' ) :
self . _add_followers ( )
if self . stage_id . done and ' stage_id ' in vals :
self . write ( { ' close_date ' : fields . Date . today ( ) } )
return res
def _add_followers ( self ) :
for request in self :
user_ids = ( request . owner_user_id + request . technician_user_id ) . ids
request . message_subscribe_users ( user_ids = user_ids )
@api.model
def _read_group_stage_ids ( self , stages , domain , order ) :
""" Read group customization in order to display all the stages in the
kanban view , even if they are empty
"""
stage_ids = stages . _search ( [ ] , order = order , access_rights_uid = SUPERUSER_ID )
return stages . browse ( stage_ids )
class MaintenanceTeam ( models . Model ) :
_name = ' maintenance.team '
_description = ' Maintenance Teams '
2018-04-05 10:25:40 +02:00
name = fields . Char ( required = True , translate = True )
2018-01-16 06:58:15 +01:00
member_ids = fields . Many2many ( ' res.users ' , ' maintenance_team_users_rel ' , string = " Team Members " )
color = fields . Integer ( " Color Index " , default = 0 )
request_ids = fields . One2many ( ' maintenance.request ' , ' maintenance_team_id ' , copy = False )
equipment_ids = fields . One2many ( ' maintenance.equipment ' , ' maintenance_team_id ' , copy = False )
# For the dashboard only
todo_request_ids = fields . One2many ( ' maintenance.request ' , string = " Requests " , copy = False , compute = ' _compute_todo_requests ' )
todo_request_count = fields . Integer ( string = " Number of Requests " , compute = ' _compute_todo_requests ' )
todo_request_count_date = fields . Integer ( string = " Number of Requests Scheduled " , compute = ' _compute_todo_requests ' )
todo_request_count_high_priority = fields . Integer ( string = " Number of Requests in High Priority " , compute = ' _compute_todo_requests ' )
todo_request_count_block = fields . Integer ( string = " Number of Requests Blocked " , compute = ' _compute_todo_requests ' )
todo_request_count_unscheduled = fields . Integer ( string = " Number of Requests Unscheduled " , compute = ' _compute_todo_requests ' )
@api.one
@api.depends ( ' request_ids.stage_id.done ' )
def _compute_todo_requests ( self ) :
self . todo_request_ids = self . request_ids . filtered ( lambda e : e . stage_id . done == False )
self . todo_request_count = len ( self . todo_request_ids )
self . todo_request_count_date = len ( self . todo_request_ids . filtered ( lambda e : e . schedule_date != False ) )
self . todo_request_count_high_priority = len ( self . todo_request_ids . filtered ( lambda e : e . priority == ' 3 ' ) )
self . todo_request_count_block = len ( self . todo_request_ids . filtered ( lambda e : e . kanban_state == ' blocked ' ) )
self . todo_request_count_unscheduled = len ( self . todo_request_ids . filtered ( lambda e : not e . schedule_date ) )
@api.one
@api.depends ( ' equipment_ids ' )
def _compute_equipment ( self ) :
self . equipment_count = len ( self . equipment_ids )