1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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 ##############################################################################
22 from osv import fields,osv
25 from mx.DateTime import RelativeDateTime, today
26 from tools.translate import _
27 import decimal_precision as dp
29 class mrp_repair(osv.osv):
31 _description = 'Repair Order'
33 def _amount_untaxed(self, cr, uid, ids, field_name, arg, context):
34 """ Calculates untaxed amount.
35 @param self: The object pointer
36 @param cr: The current row, from the database cursor,
37 @param uid: The current user ID for security checks
38 @param ids: List of selected IDs
39 @param field_name: Name of field.
41 @param context: A standard dictionary for contextual values
42 @return: Dictionary of values.
45 cur_obj = self.pool.get('res.currency')
47 for repair in self.browse(cr, uid, ids):
49 for line in repair.operations:
50 res[repair.id] += line.price_subtotal
51 for line in repair.fees_lines:
52 res[repair.id] += line.price_subtotal
53 cur = repair.pricelist_id.currency_id
54 res[repair.id] = cur_obj.round(cr, uid, cur, res[repair.id])
57 def _amount_tax(self, cr, uid, ids, field_name, arg, context):
58 """ Calculates taxed amount.
59 @param field_name: Name of field.
61 @return: Dictionary of values.
64 #return {}.fromkeys(ids, 0)
65 cur_obj = self.pool.get('res.currency')
66 tax_obj = self.pool.get('account.tax')
67 for repair in self.browse(cr, uid, ids):
69 cur = repair.pricelist_id.currency_id
70 for line in repair.operations:
72 for c in tax_obj.compute(cr, uid, line.tax_id, line.price_unit, line.product_uom_qty, repair.partner_invoice_id.id, line.product_id, repair.partner_id):
74 for line in repair.fees_lines:
76 for c in tax_obj.compute(cr, uid, line.tax_id, line.price_unit, line.product_uom_qty, repair.partner_invoice_id.id, line.product_id, repair.partner_id):
78 res[repair.id] = cur_obj.round(cr, uid, cur, val)
81 def _amount_total(self, cr, uid, ids, field_name, arg, context):
82 """ Calculates total amount.
83 @param field_name: Name of field.
85 @return: Dictionary of values.
88 untax = self._amount_untaxed(cr, uid, ids, field_name, arg, context)
89 tax = self._amount_tax(cr, uid, ids, field_name, arg, context)
90 cur_obj = self.pool.get('res.currency')
92 repair = self.browse(cr, uid, [id])[0]
93 cur = repair.pricelist_id.currency_id
94 res[id] = cur_obj.round(cr, uid, cur, untax.get(id, 0.0) + tax.get(id, 0.0))
97 def _get_default_address(self, cr, uid, ids, field_name, arg, context):
99 partner_obj = self.pool.get('res.partner')
100 for data in self.browse(cr, uid, ids):
103 adr_id = partner_obj.address_get(cr, uid, [data.partner_id.id], ['default'])['default']
104 res[data.id] = adr_id
107 def _get_lines(self, cr, uid, ids, context=None):
111 for line in self.pool.get('mrp.repair.line').browse(cr, uid, ids, context=context):
112 result[line.repair_id.id] = True
116 'name': fields.char('Repair Reference',size=24, required=True),
117 'product_id': fields.many2one('product.product', string='Product to Repair', required=True, readonly=True, states={'draft':[('readonly',False)]}),
118 'partner_id' : fields.many2one('res.partner', 'Partner', select=True, help='This field allow you to choose the parner that will be invoiced and delivered'),
119 'address_id': fields.many2one('res.partner.address', 'Delivery Address', domain="[('partner_id','=',partner_id)]"),
120 'default_address_id': fields.function(_get_default_address, method=True, type="many2one", relation="res.partner.address"),
121 'prodlot_id': fields.many2one('stock.production.lot', 'Lot Number', select=True, domain="[('product_id','=',product_id)]"),
122 'state': fields.selection([
123 ('draft','Quotation'),
124 ('confirmed','Confirmed'),
125 ('ready','Ready to Repair'),
126 ('under_repair','Under Repair'),
127 ('2binvoiced','To be Invoiced'),
128 ('invoice_except','Invoice Exception'),
131 ], 'State', readonly=True,
132 help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed repair order. \
133 \n* The \'Confirmed\' state is used when a user confirms the repair order. \
134 \n* The \'Ready to Repair\' state is used to start to repairing, user can start repairing only after repair order is confirmed. \
135 \n* The \'To be Invoiced\' state is used to generate the invoice before or after repairing done. \
136 \n* The \'Done\' state is set when repairing is completed.\
137 \n* The \'Cancelled\' state is used when user cancel repair order.'),
138 'location_id': fields.many2one('stock.location', 'Current Location', select=True, readonly=True, states={'draft':[('readonly',False)]}),
139 'location_dest_id': fields.many2one('stock.location', 'Delivery Location', readonly=True, states={'draft':[('readonly',False)]}),
140 'move_id': fields.many2one('stock.move', 'Move',required=True, domain="[('product_id','=',product_id)]", readonly=True, states={'draft':[('readonly',False)]}),
141 'guarantee_limit': fields.date('Guarantee limit', help="The guarantee limit is computed as: last move date + warranty defined on selected product. If the current date is below the guarantee limit, each operation and fee you will add will be set as 'not to invoiced' by default. Note that you can change manually afterwards."),
142 'operations' : fields.one2many('mrp.repair.line', 'repair_id', 'Operation Lines', readonly=True, states={'draft':[('readonly',False)]}),
143 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', help='The pricelist comes from the selected partner, by default.'),
144 'partner_invoice_id':fields.many2one('res.partner.address', 'Invoicing Address', domain="[('partner_id','=',partner_id)]"),
145 'invoice_method':fields.selection([
146 ("none","No Invoice"),
147 ("b4repair","Before Repair"),
148 ("after_repair","After Repair")
150 select=True, required=True, states={'draft':[('readonly',False)]}, readonly=True, help='This field allow you to change the workflow of the repair order. If value selected is different from \'No Invoice\', it also allow you to select the pricelist and invoicing address.'),
151 'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True),
152 'picking_id': fields.many2one('stock.picking', 'Picking',readonly=True),
153 'fees_lines': fields.one2many('mrp.repair.fee', 'repair_id', 'Fees Lines', readonly=True, states={'draft':[('readonly',False)]}),
154 'internal_notes': fields.text('Internal Notes'),
155 'quotation_notes': fields.text('Quotation Notes'),
156 'deliver_bool': fields.boolean('Deliver', help="Check this box if you want to manage the delivery once the product is repaired. If cheked, it will create a picking with selected product. Note that you can select the locations in the Info tab, if you have the extended view."),
157 'invoiced': fields.boolean('Invoiced', readonly=True),
158 'repaired': fields.boolean('Repaired', readonly=True),
159 'amount_untaxed': fields.function(_amount_untaxed, method=True, string='Untaxed Amount',
161 'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations'], 10),
162 'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
164 'amount_tax': fields.function(_amount_tax, method=True, string='Taxes',
166 'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations'], 10),
167 'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
169 'amount_total': fields.function(_amount_total, method=True, string='Total',
171 'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations'], 10),
172 'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
177 'state': lambda *a: 'draft',
178 'deliver_bool': lambda *a: True,
179 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'mrp.repair'),
180 'invoice_method': lambda *a: 'none',
181 'pricelist_id': lambda self, cr, uid,context : self.pool.get('product.pricelist').search(cr, uid, [('type','=','sale')])[0]
184 def copy(self, cr, uid, id, default=None, context=None):
193 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.repair'),
195 return super(mrp_repair, self).copy(cr, uid, id, default, context)
197 def onchange_product_id(self, cr, uid, ids, product_id=None):
198 """ On change of product sets some values.
199 @param product_id: Changed product
200 @return: Dictionary of values.
205 'guarantee_limit' :False,
206 'location_id': False,
207 'location_dest_id': False,
211 def onchange_move_id(self, cr, uid, ids, prod_id=False, move_id=False):
212 """ On change of move id sets values of guarantee limit, source location,
213 destination location, partner and partner address.
214 @param prod_id: Id of product in current record.
215 @param move_id: Changed move.
216 @return: Dictionary of values.
223 move = self.pool.get('stock.move').browse(cr, uid, move_id)
224 product = self.pool.get('product.product').browse(cr, uid, prod_id)
225 date = move.date_planned
226 limit = mx.DateTime.strptime(date, '%Y-%m-%d %H:%M:%S') + RelativeDateTime(months=product.warranty)
227 data['value']['guarantee_limit'] = limit.strftime('%Y-%m-%d')
228 data['value']['location_id'] = move.location_dest_id.id
229 data['value']['location_dest_id'] = move.location_dest_id.id
231 data['value']['partner_id'] = move.address_id.partner_id and move.address_id.partner_id.id
233 data['value']['partner_id'] = False
234 data['value']['address_id'] = move.address_id and move.address_id.id
235 d = self.onchange_partner_id(cr, uid, ids, data['value']['partner_id'], data['value']['address_id'])
236 data['value'].update(d['value'])
239 def button_dummy(self, cr, uid, ids, context=None):
242 def onchange_partner_id(self, cr, uid, ids, part, address_id):
243 """ On change of partner sets the values of partner address,
244 partner invoice address and pricelist.
245 @param part: Changed id of partner.
246 @param address_id: Address id from current record.
247 @return: Dictionary of values.
249 part_obj = self.pool.get('res.partner')
250 pricelist_obj = self.pool.get('product.pricelist')
254 'partner_invoice_id': False,
255 'pricelist_id': pricelist_obj.search(cr, uid, [('type','=','sale')])[0]
258 addr = part_obj.address_get(cr, uid, [part], ['delivery', 'invoice', 'default'])
259 partner = part_obj.browse(cr, uid, part)
260 pricelist = partner.property_product_pricelist and partner.property_product_pricelist.id or False
262 'address_id': address_id or addr['delivery'],
263 'partner_invoice_id': addr['invoice'],
264 'pricelist_id': pricelist
268 def onchange_lot_id(self, cr, uid, ids, lot, product_id):
269 """ On change of production lot sets the values of source location,
270 destination location, move and guarantee limit.
271 @param lot: Changed id of production lot.
272 @param product_id: Product id from current record.
273 @return: Dictionary of values.
275 move_obj = self.pool.get('stock.move')
278 'location_id': False,
279 'location_dest_id': False,
281 'guarantee_limit': False
286 move_ids = move_obj.search(cr, uid, [('prodlot_id', '=', lot)])
288 if not len(move_ids):
291 def get_last_move(lst_move):
292 while lst_move.move_dest_id and lst_move.move_dest_id.state == 'done':
293 lst_move = lst_move.move_dest_id
296 move_id = move_ids[0]
297 move = get_last_move(move_obj.browse(cr, uid, move_id))
298 data['value']['move_id'] = move.id
299 d = self.onchange_move_id(cr, uid, ids, product_id, move.id)
300 data['value'].update(d['value'])
303 def action_cancel_draft(self, cr, uid, ids, *args):
304 """ Cancels repair order when it is in 'Draft' state.
305 @param *arg: Arguments
310 mrp_line_obj = self.pool.get('mrp.repair.line')
311 for repair in self.browse(cr, uid, ids):
312 mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'draft'})
313 self.write(cr, uid, ids, {'state':'draft'})
314 wf_service = netsvc.LocalService("workflow")
316 wf_service.trg_create(uid, 'mrp.repair', id, cr)
319 def action_confirm(self, cr, uid, ids, *args):
320 """ Repair order state is set to 'To be invoiced' when invoice method
321 is 'Before repair' else state becomes 'Confirmed'.
322 @param *arg: Arguments
325 mrp_line_obj = self.pool.get('mrp.repair.line')
326 for o in self.browse(cr, uid, ids):
327 if (o.invoice_method == 'b4repair'):
328 self.write(cr, uid, [o.id], {'state': '2binvoiced'})
330 self.write(cr, uid, [o.id], {'state': 'confirmed'})
331 mrp_line_obj.write(cr, uid, [l.id for l in o.operations], {'state': 'confirmed'})
334 def action_cancel(self, cr, uid, ids, context=None):
335 """ Cancels repair order.
338 mrp_line_obj = self.pool.get('mrp.repair.line')
339 for repair in self.browse(cr, uid, ids):
340 mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'cancel'})
341 self.write(cr,uid,ids,{'state':'cancel'})
344 def wkf_invoice_create(self, cr, uid, ids, *args):
345 return self.action_invoice_create(cr, uid, ids)
347 def action_invoice_create(self, cr, uid, ids, group=False, context=None):
348 """ Creates invoice(s) for repair order.
349 @param group: It is set to true when group invoice is to be generated.
350 @return: Invoice Ids.
354 inv_line_obj = self.pool.get('account.invoice.line')
355 inv_obj = self.pool.get('account.invoice')
356 repair_line_obj = self.pool.get('mrp.repair.line')
357 repair_fee_obj = self.pool.get('mrp.repair.fee')
358 for repair in self.browse(cr, uid, ids, context=context):
359 res[repair.id] = False
360 if repair.state in ('draft','cancel') or repair.invoice_id:
362 if not (repair.partner_id.id and repair.partner_invoice_id.id):
363 raise osv.except_osv(_('No partner !'),_('You have to select a Partner Invoice Address in the repair form !'))
364 comment = repair.quotation_notes
365 if (repair.invoice_method != 'none'):
366 if group and repair.partner_invoice_id.id in invoices_group:
367 inv_id = invoices_group[repair.partner_invoice_id.id]
368 invoice = inv_obj.browse(cr, uid, inv_id)
370 'name': invoice.name +', '+repair.name,
371 'origin': invoice.origin+', '+repair.name,
372 'comment':(comment and (invoice.comment and invoice.comment+"\n"+comment or comment)) or (invoice.comment and invoice.comment or ''),
374 inv_obj.write(cr, uid, [inv_id], invoice_vals, context=context)
376 if not repair.partner_id.property_account_receivable:
377 raise osv.except_osv(_('Error !'), _('No account defined for partner "%s".') % repair.partner_id.name )
378 account_id = repair.partner_id.property_account_receivable.id
381 'origin':repair.name,
382 'type': 'out_invoice',
383 'account_id': account_id,
384 'partner_id': repair.partner_id.id,
385 'address_invoice_id': repair.address_id.id,
386 'currency_id': repair.pricelist_id.currency_id.id,
387 'comment': repair.quotation_notes,
388 'fiscal_position': repair.partner_id.property_account_position.id
390 inv_id = inv_obj.create(cr, uid, inv)
391 invoices_group[repair.partner_invoice_id.id] = inv_id
392 self.write(cr, uid, repair.id, {'invoiced': True, 'invoice_id': inv_id})
394 for operation in repair.operations:
395 if operation.to_invoice == True:
397 name = repair.name + '-' + operation.name
399 name = operation.name
401 if operation.product_id.property_account_income:
402 account_id = operation.product_id.property_account_income.id
403 elif operation.product_id.categ_id.property_account_income_categ:
404 account_id = operation.product_id.categ_id.property_account_income_categ.id
406 raise osv.except_osv(_('Error !'), _('No account defined for product "%s".') % operation.product_id.name )
408 invoice_line_id = inv_line_obj.create(cr, uid, {
409 'invoice_id': inv_id,
411 'origin': repair.name,
412 'account_id': account_id,
413 'quantity': operation.product_uom_qty,
414 'invoice_line_tax_id': [(6,0,[x.id for x in operation.tax_id])],
415 'uos_id': operation.product_uom.id,
416 'price_unit': operation.price_unit,
417 'price_subtotal': operation.product_uom_qty*operation.price_unit,
418 'product_id': operation.product_id and operation.product_id.id or False
420 repair_line_obj.write(cr, uid, [operation.id], {'invoiced': True, 'invoice_line_id': invoice_line_id})
421 for fee in repair.fees_lines:
422 if fee.to_invoice == True:
424 name = repair.name + '-' + fee.name
427 if not fee.product_id:
428 raise osv.except_osv(_('Warning !'), _('No product defined on Fees!'))
430 if fee.product_id.property_account_income:
431 account_id = fee.product_id.property_account_income.id
432 elif fee.product_id.categ_id.property_account_income_categ:
433 account_id = fee.product_id.categ_id.property_account_income_categ.id
435 raise osv.except_osv(_('Error !'), _('No account defined for product "%s".') % fee.product_id.name)
437 invoice_fee_id = inv_line_obj.create(cr, uid, {
438 'invoice_id': inv_id,
440 'origin': repair.name,
441 'account_id': account_id,
442 'quantity': fee.product_uom_qty,
443 'invoice_line_tax_id': [(6,0,[x.id for x in fee.tax_id])],
444 'uos_id': fee.product_uom.id,
445 'product_id': fee.product_id and fee.product_id.id or False,
446 'price_unit': fee.price_unit,
447 'price_subtotal': fee.product_uom_qty*fee.price_unit
449 repair_fee_obj.write(cr, uid, [fee.id], {'invoiced': True, 'invoice_line_id': invoice_fee_id})
450 res[repair.id] = inv_id
453 def action_repair_ready(self, cr, uid, ids, context=None):
454 """ Writes repair order state to 'Ready'
457 self.write(cr, uid, ids, {'state': 'ready'})
460 def action_invoice_cancel(self, cr, uid, ids, context=None):
461 """ Writes repair order state to 'Exception in invoice'
464 self.write(cr, uid, ids, {'state': 'invoice_except'})
467 def action_repair_start(self, cr, uid, ids, context=None):
468 """ Writes repair order state to 'Under Repair'
471 self.write(cr, uid, ids, {'state': 'under_repair'})
474 def action_invoice_end(self, cr, uid, ids, context=None):
475 """ Writes repair order state to 'Ready' if invoice method is Before repair.
478 for order in self.browse(cr, uid, ids):
480 if (order.invoice_method == 'b4repair'):
481 val['state'] = 'ready'
484 self.write(cr, uid, [order.id], val)
487 def action_repair_end(self, cr, uid, ids, context=None):
488 """ Writes repair order state to 'To be invoiced' if invoice method is
489 After repair else state is set to 'Ready'.
492 for order in self.browse(cr, uid, ids):
494 val['repaired'] = True
495 if (not order.invoiced and order.invoice_method=='after_repair'):
496 val['state'] = '2binvoiced'
497 elif (not order.invoiced and order.invoice_method=='b4repair'):
498 val['state'] = 'ready'
501 self.write(cr, uid, [order.id], val)
504 def wkf_repair_done(self, cr, uid, ids, *args):
505 self.action_repair_done(cr, uid, ids)
508 def action_repair_done(self, cr, uid, ids, context=None):
509 """ Creates stock move and picking for repair order.
510 @return: Picking ids.
513 move_obj = self.pool.get('stock.move')
514 wf_service = netsvc.LocalService("workflow")
515 repair_line_obj = self.pool.get('mrp.repair.line')
516 seq_obj = self.pool.get('ir.sequence')
517 pick_obj = self.pool.get('stock.picking')
518 for repair in self.browse(cr, uid, ids, context=context):
519 for move in repair.operations:
520 move_id = move_obj.create(cr, uid, {
522 'product_id': move.product_id.id,
523 'product_qty': move.product_uom_qty,
524 'product_uom': move.product_uom.id,
525 'address_id': repair.address_id and repair.address_id.id or False,
526 'location_id': move.location_id.id,
527 'location_dest_id': move.location_dest_id.id,
528 'tracking_id': False,
531 repair_line_obj.write(cr, uid, [move.id], {'move_id': move_id})
532 if repair.deliver_bool:
533 pick_name = seq_obj.get(cr, uid, 'stock.picking.out')
534 picking = pick_obj.create(cr, uid, {
536 'origin': repair.name,
539 'address_id': repair.address_id and repair.address_id.id or False,
540 'note': repair.internal_notes,
541 'invoice_state': 'none',
544 move_id = move_obj.create(cr, uid, {
546 'picking_id': picking,
547 'product_id': repair.product_id.id,
549 'product_uom': repair.product_id.uom_id.id,
550 'prodlot_id': repair.prodlot_id and repair.prodlot_id.id or False,
551 'address_id': repair.address_id and repair.address_id.id or False,
552 'location_id': repair.location_id.id,
553 'location_dest_id': repair.location_dest_id.id,
554 'tracking_id': False,
557 wf_service.trg_validate(uid, 'stock.picking', picking, 'button_confirm', cr)
558 self.write(cr, uid, [repair.id], {'state': 'done', 'picking_id': picking})
559 res[repair.id] = picking
561 self.write(cr, uid, [repair.id], {'state': 'done'})
568 class ProductChangeMixin(object):
569 def product_id_change(self, cr, uid, ids, pricelist, product, uom=False,
570 product_uom_qty=0, partner_id=False, guarantee_limit=False):
571 """ On change of product it sets product quantity, tax account, name,
572 uom of product, unit price and price subtotal.
573 @param pricelist: Pricelist of current record.
574 @param product: Changed id of product.
575 @param uom: UoM of current record.
576 @param product_uom_qty: Quantity of current record.
577 @param partner_id: Partner of current record.
578 @param guarantee_limit: Guarantee limit of current record.
579 @return: Dictionary of values and warning message.
584 if not product_uom_qty:
586 result['product_uom_qty'] = product_uom_qty
589 product_obj = self.pool.get('product.product').browse(cr, uid, product)
591 partner = self.pool.get('res.partner').browse(cr, uid, partner_id)
592 result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, product_obj.taxes_id)
594 result['name'] = product_obj.partner_ref
595 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id or False
598 'title':'No Pricelist !',
600 'You have to select a pricelist in the Repair form !\n'
601 'Please set one before choosing a product.'
604 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
605 product, product_uom_qty, partner_id, {'uom': uom,})[pricelist]
609 'title':'No valid pricelist line found !',
611 "Couldn't find a pricelist line matching this product and quantity.\n"
612 "You have to change either the product, the quantity or the pricelist."
615 result.update({'price_unit': price, 'price_subtotal': price*product_uom_qty})
617 return {'value': result, 'warning': warning}
620 class mrp_repair_line(osv.osv, ProductChangeMixin):
621 _name = 'mrp.repair.line'
622 _description = 'Repair Line'
624 def copy_data(self, cr, uid, id, default=None, context=None):
625 if not default: default = {}
626 default.update( {'invoice_line_id': False, 'move_id': False, 'invoiced': False, 'state': 'draft'})
627 return super(mrp_repair_line, self).copy_data(cr, uid, id, default, context)
629 def _amount_line(self, cr, uid, ids, field_name, arg, context):
630 """ Calculates amount.
631 @param field_name: Name of field.
633 @return: Dictionary of values.
636 cur_obj=self.pool.get('res.currency')
637 for line in self.browse(cr, uid, ids):
638 res[line.id] = line.to_invoice and line.price_unit * line.product_uom_qty or 0
639 cur = line.repair_id.pricelist_id.currency_id
640 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
644 'name' : fields.char('Description',size=64,required=True),
645 'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference',ondelete='cascade', select=True),
646 'type': fields.selection([('add','Add'),('remove','Remove')],'Type', required=True),
647 'to_invoice': fields.boolean('To Invoice'),
648 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok','=',True)], required=True),
649 'invoiced': fields.boolean('Invoiced',readonly=True),
650 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Sale Price')),
651 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',digits_compute= dp.get_precision('Sale Price')),
652 'tax_id': fields.many2many('account.tax', 'repair_operation_line_tax', 'repair_operation_line_id', 'tax_id', 'Taxes'),
653 'product_uom_qty': fields.float('Quantity (UoM)', digits=(16,2), required=True),
654 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
655 'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True),
656 'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True),
657 'location_dest_id': fields.many2one('stock.location', 'Dest. Location', required=True, select=True),
658 'move_id': fields.many2one('stock.move', 'Inventory Move', readonly=True),
659 'state': fields.selection([
661 ('confirmed','Confirmed'),
663 ('cancel','Canceled')], 'State', required=True, readonly=True,
664 help=' * The \'Draft\' state is set automatically as draft when repair order in draft state. \
665 \n* The \'Confirmed\' state is set automatically as confirm when repair order in confirm state. \
666 \n* The \'Done\' state is set automatically when repair order is completed.\
667 \n* The \'Cancelled\' state is set automatically when user cancel repair order.'),
670 'state': lambda *a: 'draft',
671 'product_uom_qty': lambda *a: 1,
674 def onchange_operation_type(self, cr, uid, ids, type, guarantee_limit):
675 """ On change of operation type it sets source location, destination location
676 and to invoice field.
677 @param product: Changed operation type.
678 @param guarantee_limit: Guarantee limit of current record.
679 @return: Dictionary of values.
683 'location_id': False,
684 'location_dest_id': False
687 produc_id = self.pool.get('stock.location').search(cr, uid, [('name','=','Production')])[0]
689 stock_id = self.pool.get('stock.location').search(cr, uid, [('name','=','Stock')])[0]
691 if guarantee_limit and today() > mx.DateTime.strptime(guarantee_limit, '%Y-%m-%d'):
694 'to_invoice': to_invoice,
695 'location_id': stock_id,
696 'location_dest_id': produc_id
701 'location_id': produc_id,
702 'location_dest_id': False
708 class mrp_repair_fee(osv.osv, ProductChangeMixin):
709 _name = 'mrp.repair.fee'
710 _description = 'Repair Fees Line'
712 def copy_data(self, cr, uid, id, default=None, context=None):
713 if not default: default = {}
714 default.update({'invoice_line_id': False, 'invoiced': False})
715 return super(mrp_repair_fee, self).copy_data(cr, uid, id, default, context)
717 def _amount_line(self, cr, uid, ids, field_name, arg, context):
718 """ Calculates amount.
719 @param field_name: Name of field.
721 @return: Dictionary of values.
724 cur_obj = self.pool.get('res.currency')
725 for line in self.browse(cr, uid, ids):
726 res[line.id] = line.to_invoice and line.price_unit * line.product_uom_qty or 0
727 cur = line.repair_id.pricelist_id.currency_id
728 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
732 'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference', required=True, ondelete='cascade', select=True),
733 'name': fields.char('Description', size=64, select=True,required=True),
734 'product_id': fields.many2one('product.product', 'Product'),
735 'product_uom_qty': fields.float('Quantity', digits=(16,2), required=True),
736 'price_unit': fields.float('Unit Price', required=True),
737 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
738 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',digits_compute= dp.get_precision('Sale Price')),
739 'tax_id': fields.many2many('account.tax', 'repair_fee_line_tax', 'repair_fee_line_id', 'tax_id', 'Taxes'),
740 'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True),
741 'to_invoice': fields.boolean('To Invoice'),
742 'invoiced': fields.boolean('Invoiced',readonly=True),
745 'to_invoice': lambda *a: True,
749 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: