1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) 2013 CodUP (<http://codup.com>).
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU Affero General Public License as
9
# published by the Free Software Foundation, either version 3 of the
10
# License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Affero General Public License for more details.
17
# You should have received a copy of the GNU Affero General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
##############################################################################
24
from openerp.osv import fields, osv
25
import openerp.addons.decimal_precision as dp
26
from openerp.tools.translate import _
27
from openerp import netsvc
29
class mro_order(osv.osv):
34
_description = 'Maintenance Order'
38
('released', 'WAITING PARTS'),
39
('ready', 'READY TO MAINTENANCE'),
40
('parts_except', 'PARTS EXCEPTION'),
42
('cancel', 'CANCELED')
45
MAINTENANCE_TYPE_SELECTION = [
51
'name': fields.char('Reference', size=64),
52
'origin': fields.char('Source Document', size=64, readonly=True, states={'draft': [('readonly', False)]},
53
help="Reference of the document that generated this maintenance order."),
54
'state': fields.selection(STATE_SELECTION, 'Status', readonly=True,
55
help="When the maintenance order is created the status is set to 'Draft'.\n\
56
If the order is confirmed the status is set to 'Waiting Parts'.\n\
57
If any exceptions are there, the status is set to 'Picking Exception'.\n\
58
If the stock is available then the status is set to 'Ready to Maintenance'.\n\
59
When the maintenance order gets started then the status is set to 'In Progress'.\n\
60
When the maintenance is over, the status is set to 'Done'."),
61
'maintenance_type': fields.selection(MAINTENANCE_TYPE_SELECTION, 'Maintenance Type', required=True, readonly=True, states={'draft': [('readonly', False)]}),
62
'task_id': fields.many2one('mro.task', 'Task', readonly=True, states={'draft': [('readonly', False)]}),
63
'description': fields.char('Description', size=64, translate=True, required=True, readonly=True, states={'draft': [('readonly', False)]}),
64
'asset_id': fields.many2one('asset.asset', 'Asset', required=True, readonly=True, states={'draft': [('readonly', False)]}),
65
'date_planned': fields.datetime('Planned Date', required=True, select=1, readonly=True, states={'draft':[('readonly',False)]}),
66
'date_scheduled': fields.datetime('Scheduled Date', required=True, select=1, readonly=True, states={'draft':[('readonly',False)],'released':[('readonly',False)],'ready':[('readonly',False)]}),
67
'date_execution': fields.datetime('Execution Date', required=True, select=1, readonly=True, states={'draft':[('readonly',False)],'released':[('readonly',False)],'ready':[('readonly',False)]}),
68
'parts_location_id': fields.many2one('stock.location', 'Parts Location', required=True,
69
readonly=True, states={'draft':[('readonly',False)]},
70
help="Location where the system will look for parts."),
71
'parts_lines': fields.one2many('mro.order.parts.line', 'maintenance_id', 'Planned parts',
72
readonly=True, states={'draft':[('readonly',False)]}),
73
'parts_picking_id': fields.many2one('stock.picking', 'Parts Picking List', readonly=True, ondelete="restrict",
74
help="This is the Internal Picking List that brings parts to the asset"),
75
'parts_ready_lines': fields.related('parts_picking_id', 'move_lines', type='one2many', relation="stock.move", string='Available Parts',
77
'parts_move_lines': fields.many2many('stock.move', 'mro_maintenance_move_ids', 'maintenance_id', 'move_id', 'Parts to Consume',
78
domain=[('state','=', ('assigned'))], readonly=True, states={'draft':[('readonly',False)]}),
79
'parts_moved_lines': fields.many2many('stock.move', 'mro_maintenance_move_ids', 'maintenance_id', 'move_id', 'Consumed Parts',
80
domain=[('state','=', ('done'))], readonly=True, states={'draft':[('readonly',False)]}),
81
'tools_description': fields.text('Tools Description',translate=True),
82
'labor_description': fields.text('Labor Description',translate=True),
83
'operations_description': fields.text('Operations Description',translate=True),
84
'documentation_description': fields.text('Documentation Description',translate=True),
85
'problem_description': fields.text('Problem Description'),
86
'company_id': fields.many2one('res.company','Company',required=True, readonly=True, states={'draft':[('readonly',False)]}),
90
'state': lambda *a: 'draft',
91
'maintenance_type': lambda *a: 'bm',
92
'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
93
'date_scheduled': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
94
'date_execution': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
95
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'mro.order', context=c),
96
'parts_location_id': lambda self, cr, uid, c: self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'stock_location_stock', context=c).id,
99
_order = 'date_execution'
101
def onchange_planned_date(self, cr, uid, ids, date):
103
onchange handler of date_planned.
106
'date_scheduled': date,
109
def onchange_scheduled_date(self, cr, uid, ids, date):
111
onchange handler of date_scheduled.
114
'date_execution': date,
117
def onchange_execution_date(self, cr, uid, ids, date, state):
119
onchange handler of date_execution.
123
value['value'] = {'date_planned': date}
125
value['value'] = {'date_scheduled': date}
128
def onchange_task(self, cr, uid, ids, task_id, parts_lines):
130
onchange handler of task_id.
132
task = self.pool.get('mro.task').browse(cr, uid, task_id)
134
new_parts_lines = [[2,line[1],line[2]] for line in parts_lines if line[0]]
135
#copy parts from task
136
for line in task.parts_lines:
137
new_parts_lines.append([0,0,{
139
'parts_id': line.parts_id.id,
140
'parts_qty': line.parts_qty,
141
'parts_uom': line.parts_uom.id,
144
'parts_lines': new_parts_lines,
145
'description': task.name,
146
'tools_description': task.tools_description,
147
'labor_description': task.labor_description,
148
'operations_description': task.operations_description,
149
'documentation_description': task.documentation_description
152
def _get_auto_picking(self, cr, uid, order):
155
def _create_picking_parts(self, cr, uid, order, context=None):
156
stock_picking = self.pool.get('stock.picking')
157
pick_type = 'internal'
160
picking_id = stock_picking.create(cr, uid, {
161
'origin': order.name,
165
'partner_id': partner_id,
166
'auto_picking': self._get_auto_picking(cr, uid, order),
167
'company_id': order.company_id.id,
169
order.write({'parts_picking_id': picking_id}, context=context)
172
def _add_picking_parts_line(self, cr, uid, parts_line, picking_parts_id, parent_move_id, context=None):
173
stock_move = self.pool.get('stock.move')
174
order = parts_line.maintenance_id
175
# Internal shipment is created for Stockable and Consumer Products
176
if parts_line.parts_id.type not in ('product', 'consu'):
178
return stock_move.create(cr, uid, {
180
'picking_id': picking_parts_id,
181
'product_id': parts_line.parts_id.id,
182
'product_qty': parts_line.parts_qty,
183
'product_uom': parts_line.parts_uom.id,
184
'date': order.date_planned,
185
'move_dest_id': parent_move_id,
186
'location_id': order.parts_location_id.id,
187
'location_dest_id': order.parts_location_id.id,
189
'company_id': order.company_id.id,
192
def _make_procurement_parts_line(self, cr, uid, parts_line, picking_parts_move_id, context=None):
193
wf_service = netsvc.LocalService("workflow")
194
procurement_order = self.pool.get('procurement.order')
195
order = parts_line.maintenance_id
196
procurement_id = procurement_order.create(cr, uid, {
198
'origin': order.name,
199
'date_planned': order.date_planned,
200
'product_id': parts_line.parts_id.id,
201
'product_qty': parts_line.parts_qty,
202
'product_uom': parts_line.parts_uom.id,
203
'location_id': order.parts_location_id.id,
204
'procure_method': parts_line.parts_id.procure_method,
205
'move_id': picking_parts_move_id,
206
'company_id': order.company_id.id,
208
wf_service.trg_validate(uid, procurement_order._name, procurement_id, 'button_confirm', cr)
209
return procurement_id
211
def _make_consume_parts_line(self, cr, uid, parts_line, context=None):
212
stock_move = self.pool.get('stock.move')
213
order = parts_line.maintenance_id
214
# Internal shipment is created for Stockable and Consumer Products
215
if parts_line.parts_id.type not in ('product', 'consu'):
217
move_id = stock_move.create(cr, uid, {
219
'date': order.date_planned,
220
'product_id': parts_line.parts_id.id,
221
'product_qty': parts_line.parts_qty,
222
'product_uom': parts_line.parts_uom.id,
223
'location_id': order.parts_location_id.id,
224
'location_dest_id': order.asset_id.property_stock_asset.id,
226
'company_id': order.company_id.id,
228
order.write({'parts_move_lines': [(4, move_id)]}, context=context)
231
def action_confirm(self, cr, uid, ids, context=None):
232
""" Confirms maintenance order.
233
@return: Newly generated Parts Picking Id.
236
wf_service = netsvc.LocalService("workflow")
237
for order in self.browse(cr, uid, ids, context=context):
238
picking_parts_id = self._create_picking_parts(cr, uid, order, context=context)
240
#make supply chain for each parts
241
for line in order.parts_lines:
242
consume_parts_move_id = self._make_consume_parts_line(cr, uid, line, context=context)
243
picking_parts_move_id = self._add_picking_parts_line(cr, uid, line, picking_parts_id, consume_parts_move_id, context=context)
244
self._make_procurement_parts_line(cr, uid, line, picking_parts_move_id, context=context)
246
wf_service.trg_validate(uid, 'stock.picking', picking_parts_id, 'button_confirm', cr)
247
order.write({'state':'released'}, context=context)
248
return picking_parts_id
250
def action_ready(self, cr, uid, ids):
251
self.write(cr, uid, ids, {'state': 'ready'})
254
def action_parts_except(self, cr, uid, ids, context=None):
255
for order in self.browse(cr, uid, ids, context=context):
256
self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in order.parts_move_lines])
257
self.write(cr, uid, ids, {'state': 'parts_except'})
260
def action_done(self, cr, uid, ids, context=None):
261
for order in self.browse(cr, uid, ids, context=context):
262
self.pool.get('stock.move').action_done(cr, uid, [x.id for x in order.parts_move_lines])
263
self.write(cr, uid, ids, {'state': 'done', 'date_execution': time.strftime('%Y-%m-%d %H:%M:%S')})
266
def action_cancel(self, cr, uid, ids, context=None):
267
for order in self.browse(cr, uid, ids, context=context):
268
if order.state == 'released' and order.parts_picking_id.state not in ('draft', 'cancel'):
269
raise osv.except_osv(
270
_('Cannot cancel maintenance order!'),
271
_('You must first cancel related internal picking attached to this maintenance order.'))
272
self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in order.parts_move_lines])
273
self.write(cr, uid, ids, {'state': 'cancel'})
276
def test_if_parts(self, cr, uid, ids):
278
@return: True or False
281
for order in self.browse(cr, uid, ids):
282
if not order.parts_lines:
286
def force_done(self, cr, uid, ids, context=None):
287
""" Assign and consume parts.
290
self.force_parts_reservation(cr, uid, ids)
291
wf_service = netsvc.LocalService("workflow")
292
for order in self.browse(cr, uid, ids, context=context):
293
wf_service.trg_validate(uid, 'mro.order', order.id, 'button_done', cr)
296
def force_parts_reservation(self, cr, uid, ids, context=None):
300
pick_obj = self.pool.get('stock.picking')
301
pick_obj.force_assign(cr, uid, [order.parts_picking_id.id for order in self.browse(cr, uid, ids)])
304
def create(self, cr, uid, vals, context=None):
305
if vals.get('name','/')=='/':
306
vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'mro.order') or '/'
307
return super(mro_order, self).create(cr, uid, vals, context=context)
309
def write(self, cr, uid, ids, vals, context=None):
310
if vals.get('date_execution') and not vals.get('state'):
311
# constraint for calendar view
312
for order in self.browse(cr, uid, ids):
313
if order.state == 'draft':
314
vals['date_planned'] = vals['date_execution']
315
vals['date_scheduled'] = vals['date_execution']
316
elif order.state in ('released','ready'):
317
vals['date_scheduled'] = vals['date_execution']
318
else: del vals['date_execution']
319
return super(mro_order, self).write(cr, uid, ids, vals, context=context)
322
class mro_order_parts_line(osv.osv):
323
_name = 'mro.order.parts.line'
324
_description = 'Maintenance Planned Parts'
326
'name': fields.char('Description', size=64),
327
'parts_id': fields.many2one('product.product', 'Parts', required=True),
328
'parts_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
329
'parts_uom': fields.many2one('product.uom', 'Unit of Measure', required=True),
330
'maintenance_id': fields.many2one('mro.order', 'Maintenance Order', select=True),
334
'parts_qty': lambda *a: 1.0,
337
def onchange_parts(self, cr, uid, ids, parts_id):
339
onchange handler of parts_id.
341
parts = self.pool.get('product.product').browse(cr, uid, parts_id)
342
return {'value': {'parts_uom': parts.uom_id.id}}
344
def unlink(self, cr, uid, ids, context=None):
345
self.write(cr, uid, ids, {'maintenance_id': False})
348
def create(self, cr, uid, values, context=None):
349
ids = self.search(cr, uid, [('maintenance_id','=',values['maintenance_id']),('parts_id','=',values['parts_id'])])
351
values['parts_qty'] = self.browse(cr, uid, ids[0]).parts_qty + values['parts_qty']
352
self.write(cr, uid, ids[0], values, context=context)
354
ids = self.search(cr, uid, [('maintenance_id','=',False)])
356
self.write(cr, uid, ids[0], values, context=context)
358
return super(mro_order_parts_line, self).create(cr, uid, values, context=context)
361
class mro_task(osv.osv):
363
Maintenance Tasks (Template for order)
366
_description = 'Maintenance Task'
368
MAINTENANCE_TYPE_SELECTION = [
373
'name': fields.char('Description', size=64, required=True, translate=True),
374
'asset_id': fields.many2one('asset.asset', 'Asset', required=True),
375
'maintenance_type': fields.selection(MAINTENANCE_TYPE_SELECTION, 'Maintenance Type', required=True),
376
'parts_lines': fields.one2many('mro.task.parts.line', 'task_id', 'Parts'),
377
'tools_description': fields.text('Tools Description',translate=True),
378
'labor_description': fields.text('Labor Description',translate=True),
379
'operations_description': fields.text('Operations Description',translate=True),
380
'documentation_description': fields.text('Documentation Description',translate=True),
381
'active': fields.boolean('Active'),
386
'maintenance_type': 'cm',
390
class mro_task_parts_line(osv.osv):
391
_name = 'mro.task.parts.line'
392
_description = 'Maintenance Planned Parts'
394
'name': fields.char('Description', size=64),
395
'parts_id': fields.many2one('product.product', 'Parts', required=True),
396
'parts_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
397
'parts_uom': fields.many2one('product.uom', 'Unit of Measure', required=True),
398
'task_id': fields.many2one('mro.task', 'Maintenance Task', select=True),
402
'parts_qty': lambda *a: 1.0,
405
def onchange_parts(self, cr, uid, ids, parts_id):
407
onchange handler of parts_id.
409
parts = self.pool.get('product.product').browse(cr, uid, parts_id)
410
return {'value': {'parts_uom': parts.uom_id.id}}
412
def unlink(self, cr, uid, ids, context=None):
413
self.write(cr, uid, ids, {'task_id': False})
416
def create(self, cr, uid, values, context=None):
417
ids = self.search(cr, uid, [('task_id','=',values['task_id']),('parts_id','=',values['parts_id'])])
419
values['parts_qty'] = self.browse(cr, uid, ids[0]).parts_qty + values['parts_qty']
420
self.write(cr, uid, ids[0], values, context=context)
422
ids = self.search(cr, uid, [('task_id','=',False)])
424
self.write(cr, uid, ids[0], values, context=context)
426
return super(mro_task_parts_line, self).create(cr, uid, values, context=context)
429
class mro_request(osv.osv):
433
_name = 'mro.request'
434
_description = 'Maintenance Request'
439
('run', 'Execution'),
441
('reject', 'Rejected'),
442
('cancel', 'Canceled')
446
'name': fields.char('Reference', size=64),
447
'state': fields.selection(STATE_SELECTION, 'Status', readonly=True,
448
help="When the maintenance request is created the status is set to 'Draft'.\n\
449
If the request is sent the status is set to 'Claim'.\n\
450
If the request is confirmed the status is set to 'Execution'.\n\
451
If the request is rejected the status is set to 'Rejected'.\n\
452
When the maintenance is over, the status is set to 'Done'."),
453
'asset_id': fields.many2one('asset.asset', 'Asset', required=True, readonly=True, states={'draft': [('readonly', False)]}),
454
'cause': fields.char('Cause', size=64, translate=True, required=True, readonly=True, states={'draft': [('readonly', False)]}),
455
'description': fields.text('Description', readonly=True, states={'draft': [('readonly', False)]}),
456
'reject_reason': fields.text('Reject Reason', readonly=True),
457
'requested_date': fields.datetime('Requested Date', required=True, select=1, readonly=True, states={'draft': [('readonly', False)]}, help="Date requested by the customer for maintenance."),
458
'execution_date': fields.datetime('Execution Date', required=True, select=1, readonly=True, states={'draft':[('readonly',False)],'claim':[('readonly',False)]}),
459
'breakdown': fields.boolean('Breakdown', readonly=True, states={'draft': [('readonly', False)]}),
460
'create_uid': fields.many2one('res.users', 'Responsible'),
465
'requested_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
466
'execution_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
470
def onchange_requested_date(self, cr, uid, ids, date):
472
onchange handler of requested_date.
475
'execution_date': date,
478
def onchange_execution_date(self, cr, uid, ids, date, state, breakdown):
480
onchange handler of execution_date.
483
if state == 'draft' and not breakdown:
484
value['value'] = {'requested_date': date}
487
def action_send(self, cr, uid, ids, context=None):
488
value = {'state': 'claim'}
489
for request in self.browse(cr, uid, ids, context=context):
490
if request.breakdown:
491
value['requested_date'] = time.strftime('%Y-%m-%d %H:%M:%S')
492
self.write(cr, uid, ids, value)
495
def action_confirm(self, cr, uid, ids, context=None):
496
""" Confirms maintenance request.
497
@return: Newly generated Maintenance Order Id.
499
order = self.pool.get('mro.order')
501
for request in self.browse(cr, uid, ids, context=context):
502
order_id = order.create(cr, uid, {
503
'date_planned':request.requested_date,
504
'date_scheduled':request.requested_date,
505
'date_execution':request.requested_date,
506
'origin': request.name,
508
'maintenance_type': 'bm',
509
'asset_id': request.asset_id.id,
510
'description': request.cause,
511
'problem_description': request.description,
513
self.write(cr, uid, ids, {'state': 'run'})
516
def action_done(self, cr, uid, ids, context=None):
517
self.write(cr, uid, ids, {'state': 'done', 'execution_date': time.strftime('%Y-%m-%d %H:%M:%S')})
520
def action_reject(self, cr, uid, ids, context=None):
521
self.write(cr, uid, ids, {'state': 'reject', 'execution_date': time.strftime('%Y-%m-%d %H:%M:%S')})
524
def action_cancel(self, cr, uid, ids, context=None):
525
self.write(cr, uid, ids, {'state': 'cancel', 'execution_date': time.strftime('%Y-%m-%d %H:%M:%S')})
528
def create(self, cr, uid, vals, context=None):
529
if vals.get('name','/')=='/':
530
vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'mro.request') or '/'
531
return super(mro_request, self).create(cr, uid, vals, context=context)
533
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
b'\\ No newline at end of file'