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
24 from datetime import datetime
25 from dateutil.relativedelta import relativedelta
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=None):
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, context=context):
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=None):
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, context=context):
69 cur = repair.pricelist_id.currency_id
70 for line in repair.operations:
71 #manage prices with tax included use compute_all instead of compute
73 tax_calculate = tax_obj.compute_all(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 c in tax_calculate['taxes']:
76 for line in repair.fees_lines:
78 tax_calculate = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, line.product_uom_qty, repair.partner_invoice_id.id, line.product_id, repair.partner_id)
79 for c in tax_calculate['taxes']:
81 res[repair.id] = cur_obj.round(cr, uid, cur, val)
84 def _amount_total(self, cr, uid, ids, field_name, arg, context=None):
85 """ Calculates total amount.
86 @param field_name: Name of field.
88 @return: Dictionary of values.
91 untax = self._amount_untaxed(cr, uid, ids, field_name, arg, context=context)
92 tax = self._amount_tax(cr, uid, ids, field_name, arg, context=context)
93 cur_obj = self.pool.get('res.currency')
95 repair = self.browse(cr, uid, id, context=context)
96 cur = repair.pricelist_id.currency_id
97 res[id] = cur_obj.round(cr, uid, cur, untax.get(id, 0.0) + tax.get(id, 0.0))
100 def _get_default_address(self, cr, uid, ids, field_name, arg, context=None):
102 partner_obj = self.pool.get('res.partner')
103 for data in self.browse(cr, uid, ids, context=context):
106 adr_id = partner_obj.address_get(cr, uid, [data.partner_id.id], ['default'])['default']
107 res[data.id] = adr_id
110 def _get_lines(self, cr, uid, ids, context=None):
112 for line in self.pool.get('mrp.repair.line').browse(cr, uid, ids, context=context):
113 result[line.repair_id.id] = True
117 'name': fields.char('Repair Reference',size=24, required=True),
118 'product_id': fields.many2one('product.product', string='Product to Repair', required=True, readonly=True, states={'draft':[('readonly',False)]}),
119 'partner_id' : fields.many2one('res.partner', 'Partner', select=True, help='This field allow you to choose the parner that will be invoiced and delivered'),
120 'address_id': fields.many2one('res.partner.address', 'Delivery Address', domain="[('partner_id','=',partner_id)]"),
121 'default_address_id': fields.function(_get_default_address, type="many2one", relation="res.partner.address"),
122 'prodlot_id': fields.many2one('stock.production.lot', 'Lot Number', select=True, domain="[('product_id','=',product_id)]"),
123 'state': fields.selection([
124 ('draft','Quotation'),
125 ('confirmed','Confirmed'),
126 ('ready','Ready to Repair'),
127 ('under_repair','Under Repair'),
128 ('2binvoiced','To be Invoiced'),
129 ('invoice_except','Invoice Exception'),
132 ], 'State', readonly=True,
133 help=' * The \'Draft\' state is used when a user is encoding a new and unconfirmed repair order. \
134 \n* The \'Confirmed\' state is used when a user confirms the repair order. \
135 \n* The \'Ready to Repair\' state is used to start to repairing, user can start repairing only after repair order is confirmed. \
136 \n* The \'To be Invoiced\' state is used to generate the invoice before or after repairing done. \
137 \n* The \'Done\' state is set when repairing is completed.\
138 \n* The \'Cancelled\' state is used when user cancel repair order.'),
139 'location_id': fields.many2one('stock.location', 'Current Location', select=True, readonly=True, states={'draft':[('readonly',False)]}),
140 'location_dest_id': fields.many2one('stock.location', 'Delivery Location', readonly=True, states={'draft':[('readonly',False)]}),
141 'move_id': fields.many2one('stock.move', 'Move',required=True, domain="[('product_id','=',product_id)]", readonly=True, states={'draft':[('readonly',False)]}),
142 '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."),
143 'operations' : fields.one2many('mrp.repair.line', 'repair_id', 'Operation Lines', readonly=True, states={'draft':[('readonly',False)]}),
144 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', help='The pricelist comes from the selected partner, by default.'),
145 'partner_invoice_id':fields.many2one('res.partner.address', 'Invoicing Address', domain="[('partner_id','=',partner_id)]"),
146 'invoice_method':fields.selection([
147 ("none","No Invoice"),
148 ("b4repair","Before Repair"),
149 ("after_repair","After Repair")
151 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.'),
152 'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True),
153 'picking_id': fields.many2one('stock.picking', 'Picking',readonly=True),
154 'fees_lines': fields.one2many('mrp.repair.fee', 'repair_id', 'Fees Lines', readonly=True, states={'draft':[('readonly',False)]}),
155 'internal_notes': fields.text('Internal Notes'),
156 'quotation_notes': fields.text('Quotation Notes'),
157 'company_id': fields.many2one('res.company', 'Company'),
158 '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."),
159 'invoiced': fields.boolean('Invoiced', readonly=True),
160 'repaired': fields.boolean('Repaired', readonly=True),
161 'amount_untaxed': fields.function(_amount_untaxed, string='Untaxed Amount',
163 'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations'], 10),
164 'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
166 'amount_tax': fields.function(_amount_tax, string='Taxes',
168 'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations'], 10),
169 'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
171 'amount_total': fields.function(_amount_total, string='Total',
173 'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations'], 10),
174 'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
179 'state': lambda *a: 'draft',
180 'deliver_bool': lambda *a: True,
181 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'mrp.repair'),
182 'invoice_method': lambda *a: 'none',
183 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.repair', context=context),
184 'pricelist_id': lambda self, cr, uid,context : self.pool.get('product.pricelist').search(cr, uid, [('type','=','sale')])[0]
187 def copy(self, cr, uid, id, default=None, context=None):
196 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.repair'),
198 return super(mrp_repair, self).copy(cr, uid, id, default, context)
200 def onchange_product_id(self, cr, uid, ids, product_id=None):
201 """ On change of product sets some values.
202 @param product_id: Changed product
203 @return: Dictionary of values.
208 'guarantee_limit' :False,
209 'location_id': False,
210 'location_dest_id': False,
214 def onchange_move_id(self, cr, uid, ids, prod_id=False, move_id=False):
215 """ On change of move id sets values of guarantee limit, source location,
216 destination location, partner and partner address.
217 @param prod_id: Id of product in current record.
218 @param move_id: Changed move.
219 @return: Dictionary of values.
226 move = self.pool.get('stock.move').browse(cr, uid, move_id)
227 product = self.pool.get('product.product').browse(cr, uid, prod_id)
228 limit = datetime.strptime(move.date_expected, '%Y-%m-%d %H:%M:%S') + relativedelta(months=int(product.warranty))
229 data['value']['guarantee_limit'] = limit.strftime('%Y-%m-%d')
230 data['value']['location_id'] = move.location_dest_id.id
231 data['value']['location_dest_id'] = move.location_dest_id.id
233 data['value']['partner_id'] = move.address_id.partner_id and move.address_id.partner_id.id
235 data['value']['partner_id'] = False
236 data['value']['address_id'] = move.address_id and move.address_id.id
237 d = self.onchange_partner_id(cr, uid, ids, data['value']['partner_id'], data['value']['address_id'])
238 data['value'].update(d['value'])
241 def button_dummy(self, cr, uid, ids, context=None):
244 def onchange_partner_id(self, cr, uid, ids, part, address_id):
245 """ On change of partner sets the values of partner address,
246 partner invoice address and pricelist.
247 @param part: Changed id of partner.
248 @param address_id: Address id from current record.
249 @return: Dictionary of values.
251 part_obj = self.pool.get('res.partner')
252 pricelist_obj = self.pool.get('product.pricelist')
256 'partner_invoice_id': False,
257 'pricelist_id': pricelist_obj.search(cr, uid, [('type','=','sale')])[0]
260 addr = part_obj.address_get(cr, uid, [part], ['delivery', 'invoice', 'default'])
261 partner = part_obj.browse(cr, uid, part)
262 pricelist = partner.property_product_pricelist and partner.property_product_pricelist.id or False
264 'address_id': address_id or addr['delivery'],
265 'partner_invoice_id': addr['invoice'],
266 'pricelist_id': pricelist
270 def onchange_lot_id(self, cr, uid, ids, lot, product_id):
271 """ On change of production lot sets the values of source location,
272 destination location, move and guarantee limit.
273 @param lot: Changed id of production lot.
274 @param product_id: Product id from current record.
275 @return: Dictionary of values.
277 move_obj = self.pool.get('stock.move')
280 'location_id': False,
281 'location_dest_id': False,
283 'guarantee_limit': False
288 move_ids = move_obj.search(cr, uid, [('prodlot_id', '=', lot)])
290 if not len(move_ids):
293 def get_last_move(lst_move):
294 while lst_move.move_dest_id and lst_move.move_dest_id.state == 'done':
295 lst_move = lst_move.move_dest_id
298 move_id = move_ids[0]
299 move = get_last_move(move_obj.browse(cr, uid, move_id))
300 data['value']['move_id'] = move.id
301 d = self.onchange_move_id(cr, uid, ids, product_id, move.id)
302 data['value'].update(d['value'])
305 def action_cancel_draft(self, cr, uid, ids, *args):
306 """ Cancels repair order when it is in 'Draft' state.
307 @param *arg: Arguments
312 mrp_line_obj = self.pool.get('mrp.repair.line')
313 for repair in self.browse(cr, uid, ids):
314 mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'draft'})
315 self.write(cr, uid, ids, {'state':'draft'})
316 wf_service = netsvc.LocalService("workflow")
318 wf_service.trg_create(uid, 'mrp.repair', id, cr)
321 def action_confirm(self, cr, uid, ids, *args):
322 """ Repair order state is set to 'To be invoiced' when invoice method
323 is 'Before repair' else state becomes 'Confirmed'.
324 @param *arg: Arguments
327 mrp_line_obj = self.pool.get('mrp.repair.line')
328 for o in self.browse(cr, uid, ids):
329 if (o.invoice_method == 'b4repair'):
330 self.write(cr, uid, [o.id], {'state': '2binvoiced'})
332 self.write(cr, uid, [o.id], {'state': 'confirmed'})
334 raise osv.except_osv(_('Error !'),_('You cannot confirm a repair order which has no line.'))
335 for line in o.operations:
336 if line.product_id.track_production and not line.prodlot_id:
337 raise osv.except_osv(_('Warning'), _("Production lot is required for opration line with product '%s'") % (line.product_id.name))
338 mrp_line_obj.write(cr, uid, [l.id for l in o.operations], {'state': 'confirmed'})
341 def action_cancel(self, cr, uid, ids, context=None):
342 """ Cancels repair order.
345 mrp_line_obj = self.pool.get('mrp.repair.line')
346 for repair in self.browse(cr, uid, ids, context=context):
347 mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'cancel'}, context=context)
348 self.write(cr,uid,ids,{'state':'cancel'})
351 def wkf_invoice_create(self, cr, uid, ids, *args):
352 return self.action_invoice_create(cr, uid, ids)
354 def action_invoice_create(self, cr, uid, ids, group=False, context=None):
355 """ Creates invoice(s) for repair order.
356 @param group: It is set to true when group invoice is to be generated.
357 @return: Invoice Ids.
361 inv_line_obj = self.pool.get('account.invoice.line')
362 inv_obj = self.pool.get('account.invoice')
363 repair_line_obj = self.pool.get('mrp.repair.line')
364 repair_fee_obj = self.pool.get('mrp.repair.fee')
365 for repair in self.browse(cr, uid, ids, context=context):
366 res[repair.id] = False
367 if repair.state in ('draft','cancel') or repair.invoice_id:
369 if not (repair.partner_id.id and repair.partner_invoice_id.id):
370 raise osv.except_osv(_('No partner !'),_('You have to select a Partner Invoice Address in the repair form !'))
371 comment = repair.quotation_notes
372 if (repair.invoice_method != 'none'):
373 if group and repair.partner_invoice_id.id in invoices_group:
374 inv_id = invoices_group[repair.partner_invoice_id.id]
375 invoice = inv_obj.browse(cr, uid, inv_id)
377 'name': invoice.name +', '+repair.name,
378 'origin': invoice.origin+', '+repair.name,
379 'comment':(comment and (invoice.comment and invoice.comment+"\n"+comment or comment)) or (invoice.comment and invoice.comment or ''),
381 inv_obj.write(cr, uid, [inv_id], invoice_vals, context=context)
383 if not repair.partner_id.property_account_receivable:
384 raise osv.except_osv(_('Error !'), _('No account defined for partner "%s".') % repair.partner_id.name )
385 account_id = repair.partner_id.property_account_receivable.id
388 'origin':repair.name,
389 'type': 'out_invoice',
390 'account_id': account_id,
391 'partner_id': repair.partner_id.id,
392 'address_invoice_id': repair.address_id.id,
393 'currency_id': repair.pricelist_id.currency_id.id,
394 'comment': repair.quotation_notes,
395 'fiscal_position': repair.partner_id.property_account_position.id
397 inv_id = inv_obj.create(cr, uid, inv)
398 invoices_group[repair.partner_invoice_id.id] = inv_id
399 self.write(cr, uid, repair.id, {'invoiced': True, 'invoice_id': inv_id})
401 for operation in repair.operations:
402 if operation.to_invoice == True:
404 name = repair.name + '-' + operation.name
406 name = operation.name
408 if operation.product_id.property_account_income:
409 account_id = operation.product_id.property_account_income.id
410 elif operation.product_id.categ_id.property_account_income_categ:
411 account_id = operation.product_id.categ_id.property_account_income_categ.id
413 raise osv.except_osv(_('Error !'), _('No account defined for product "%s".') % operation.product_id.name )
415 invoice_line_id = inv_line_obj.create(cr, uid, {
416 'invoice_id': inv_id,
418 'origin': repair.name,
419 'account_id': account_id,
420 'quantity': operation.product_uom_qty,
421 'invoice_line_tax_id': [(6,0,[x.id for x in operation.tax_id])],
422 'uos_id': operation.product_uom.id,
423 'price_unit': operation.price_unit,
424 'price_subtotal': operation.product_uom_qty*operation.price_unit,
425 'product_id': operation.product_id and operation.product_id.id or False
427 repair_line_obj.write(cr, uid, [operation.id], {'invoiced': True, 'invoice_line_id': invoice_line_id})
428 for fee in repair.fees_lines:
429 if fee.to_invoice == True:
431 name = repair.name + '-' + fee.name
434 if not fee.product_id:
435 raise osv.except_osv(_('Warning !'), _('No product defined on Fees!'))
437 if fee.product_id.property_account_income:
438 account_id = fee.product_id.property_account_income.id
439 elif fee.product_id.categ_id.property_account_income_categ:
440 account_id = fee.product_id.categ_id.property_account_income_categ.id
442 raise osv.except_osv(_('Error !'), _('No account defined for product "%s".') % fee.product_id.name)
444 invoice_fee_id = inv_line_obj.create(cr, uid, {
445 'invoice_id': inv_id,
447 'origin': repair.name,
448 'account_id': account_id,
449 'quantity': fee.product_uom_qty,
450 'invoice_line_tax_id': [(6,0,[x.id for x in fee.tax_id])],
451 'uos_id': fee.product_uom.id,
452 'product_id': fee.product_id and fee.product_id.id or False,
453 'price_unit': fee.price_unit,
454 'price_subtotal': fee.product_uom_qty*fee.price_unit
456 repair_fee_obj.write(cr, uid, [fee.id], {'invoiced': True, 'invoice_line_id': invoice_fee_id})
457 res[repair.id] = inv_id
460 def action_repair_ready(self, cr, uid, ids, context=None):
461 """ Writes repair order state to 'Ready'
464 for repair in self.browse(cr, uid, ids, context=context):
465 self.pool.get('mrp.repair.line').write(cr, uid, [l.id for
466 l in repair.operations], {'state': 'confirmed'}, context=context)
467 self.write(cr, uid, [repair.id], {'state': 'ready'})
470 def action_repair_start(self, cr, uid, ids, context=None):
471 """ Writes repair order state to 'Under Repair'
474 repair_line = self.pool.get('mrp.repair.line')
475 for repair in self.browse(cr, uid, ids, context=context):
476 repair_line.write(cr, uid, [l.id for
477 l in repair.operations], {'state': 'confirmed'}, context=context)
478 repair.write({'state': 'under_repair'})
481 def action_repair_end(self, cr, uid, ids, context=None):
482 """ Writes repair order state to 'To be invoiced' if invoice method is
483 After repair else state is set to 'Ready'.
486 for order in self.browse(cr, uid, ids, context=context):
488 val['repaired'] = True
489 if (not order.invoiced and order.invoice_method=='after_repair'):
490 val['state'] = '2binvoiced'
491 elif (not order.invoiced and order.invoice_method=='b4repair'):
492 val['state'] = 'ready'
495 self.write(cr, uid, [order.id], val)
498 def wkf_repair_done(self, cr, uid, ids, *args):
499 self.action_repair_done(cr, uid, ids)
502 def action_repair_done(self, cr, uid, ids, context=None):
503 """ Creates stock move and picking for repair order.
504 @return: Picking ids.
507 move_obj = self.pool.get('stock.move')
508 wf_service = netsvc.LocalService("workflow")
509 repair_line_obj = self.pool.get('mrp.repair.line')
510 seq_obj = self.pool.get('ir.sequence')
511 pick_obj = self.pool.get('stock.picking')
512 for repair in self.browse(cr, uid, ids, context=context):
513 for move in repair.operations:
514 move_id = move_obj.create(cr, uid, {
516 'product_id': move.product_id.id,
517 'product_qty': move.product_uom_qty,
518 'product_uom': move.product_uom.id,
519 'address_id': repair.address_id and repair.address_id.id or False,
520 'location_id': move.location_id.id,
521 'location_dest_id': move.location_dest_id.id,
522 'tracking_id': False,
523 'prodlot_id': move.prodlot_id and move.prodlot_id.id or False,
526 repair_line_obj.write(cr, uid, [move.id], {'move_id': move_id, 'state': 'done'}, context=context)
527 if repair.deliver_bool:
528 pick_name = seq_obj.get(cr, uid, 'stock.picking.out')
529 picking = pick_obj.create(cr, uid, {
531 'origin': repair.name,
534 'address_id': repair.address_id and repair.address_id.id or False,
535 'note': repair.internal_notes,
536 'invoice_state': 'none',
539 move_id = move_obj.create(cr, uid, {
541 'picking_id': picking,
542 'product_id': repair.product_id.id,
543 'product_qty': move.product_uom_qty or 1.0,
544 'product_uom': repair.product_id.uom_id.id,
545 'prodlot_id': repair.prodlot_id and repair.prodlot_id.id or False,
546 'address_id': repair.address_id and repair.address_id.id or False,
547 'location_id': repair.location_id.id,
548 'location_dest_id': repair.location_dest_id.id,
549 'tracking_id': False,
552 wf_service.trg_validate(uid, 'stock.picking', picking, 'button_confirm', cr)
553 self.write(cr, uid, [repair.id], {'state': 'done', 'picking_id': picking})
554 res[repair.id] = picking
556 self.write(cr, uid, [repair.id], {'state': 'done'})
563 class ProductChangeMixin(object):
564 def product_id_change(self, cr, uid, ids, pricelist, product, uom=False,
565 product_uom_qty=0, partner_id=False, guarantee_limit=False):
566 """ On change of product it sets product quantity, tax account, name,
567 uom of product, unit price and price subtotal.
568 @param pricelist: Pricelist of current record.
569 @param product: Changed id of product.
570 @param uom: UoM of current record.
571 @param product_uom_qty: Quantity of current record.
572 @param partner_id: Partner of current record.
573 @param guarantee_limit: Guarantee limit of current record.
574 @return: Dictionary of values and warning message.
579 if not product_uom_qty:
581 result['product_uom_qty'] = product_uom_qty
584 product_obj = self.pool.get('product.product').browse(cr, uid, product)
586 partner = self.pool.get('res.partner').browse(cr, uid, partner_id)
587 result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, product_obj.taxes_id)
589 result['name'] = product_obj.partner_ref
590 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id or False
593 'title':'No Pricelist !',
595 'You have to select a pricelist in the Repair form !\n'
596 'Please set one before choosing a product.'
599 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
600 product, product_uom_qty, partner_id, {'uom': uom,})[pricelist]
604 'title':'No valid pricelist line found !',
606 "Couldn't find a pricelist line matching this product and quantity.\n"
607 "You have to change either the product, the quantity or the pricelist."
610 result.update({'price_unit': price, 'price_subtotal': price*product_uom_qty})
612 return {'value': result, 'warning': warning}
615 class mrp_repair_line(osv.osv, ProductChangeMixin):
616 _name = 'mrp.repair.line'
617 _description = 'Repair Line'
619 def copy_data(self, cr, uid, id, default=None, context=None):
620 if not default: default = {}
621 default.update( {'invoice_line_id': False, 'move_id': False, 'invoiced': False, 'state': 'draft'})
622 return super(mrp_repair_line, self).copy_data(cr, uid, id, default, context)
624 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
625 """ Calculates amount.
626 @param field_name: Name of field.
628 @return: Dictionary of values.
631 cur_obj=self.pool.get('res.currency')
632 for line in self.browse(cr, uid, ids, context=context):
633 res[line.id] = line.to_invoice and line.price_unit * line.product_uom_qty or 0
634 cur = line.repair_id.pricelist_id.currency_id
635 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
639 'name' : fields.char('Description',size=64,required=True),
640 'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference',ondelete='cascade', select=True),
641 'type': fields.selection([('add','Add'),('remove','Remove')],'Type', required=True),
642 'to_invoice': fields.boolean('To Invoice'),
643 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok','=',True)], required=True),
644 'invoiced': fields.boolean('Invoiced',readonly=True),
645 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Sale Price')),
646 'price_subtotal': fields.function(_amount_line, string='Subtotal',digits_compute= dp.get_precision('Sale Price')),
647 'tax_id': fields.many2many('account.tax', 'repair_operation_line_tax', 'repair_operation_line_id', 'tax_id', 'Taxes'),
648 'product_uom_qty': fields.float('Quantity (UoM)', digits=(16,2), required=True),
649 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
650 'prodlot_id': fields.many2one('stock.production.lot', 'Lot Number',domain="[('product_id','=',product_id)]"),
651 'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True),
652 'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True),
653 'location_dest_id': fields.many2one('stock.location', 'Dest. Location', required=True, select=True),
654 'move_id': fields.many2one('stock.move', 'Inventory Move', readonly=True),
655 'state': fields.selection([
657 ('confirmed','Confirmed'),
659 ('cancel','Canceled')], 'State', required=True, readonly=True,
660 help=' * The \'Draft\' state is set automatically as draft when repair order in draft state. \
661 \n* The \'Confirmed\' state is set automatically as confirm when repair order in confirm state. \
662 \n* The \'Done\' state is set automatically when repair order is completed.\
663 \n* The \'Cancelled\' state is set automatically when user cancel repair order.'),
666 'state': lambda *a: 'draft',
667 'product_uom_qty': lambda *a: 1,
670 def onchange_operation_type(self, cr, uid, ids, type, guarantee_limit, company_id=False, context=None):
671 """ On change of operation type it sets source location, destination location
672 and to invoice field.
673 @param product: Changed operation type.
674 @param guarantee_limit: Guarantee limit of current record.
675 @return: Dictionary of values.
679 'location_id': False,
680 'location_dest_id': False
682 warehouse_obj = self.pool.get('stock.warehouse')
683 location_id = self.pool.get('stock.location').search(cr, uid, [('usage','=','production')], context=context)
684 location_id = location_id and location_id[0] or False
687 # TOCHECK: Find stock location for user's company warehouse or
688 # repair order's company's warehouse (company_id field is added in fix of lp:831583)
689 args = company_id and [('company_id', '=', company_id)] or []
690 warehouse_ids = warehouse_obj.search(cr, uid, args, context=context)
693 stock_id = warehouse_obj.browse(cr, uid, warehouse_ids[0], context=context).lot_stock_id.id
694 to_invoice = (guarantee_limit and datetime.strptime(guarantee_limit, '%Y-%m-%d') < datetime.now())
697 'to_invoice': to_invoice,
698 'location_id': stock_id,
699 'location_dest_id': location_id
704 'location_id': location_id,
705 'location_dest_id': False
710 class mrp_repair_fee(osv.osv, ProductChangeMixin):
711 _name = 'mrp.repair.fee'
712 _description = 'Repair Fees Line'
714 def copy_data(self, cr, uid, id, default=None, context=None):
715 if not default: default = {}
716 default.update({'invoice_line_id': False, 'invoiced': False})
717 return super(mrp_repair_fee, self).copy_data(cr, uid, id, default, context)
719 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
720 """ Calculates amount.
721 @param field_name: Name of field.
723 @return: Dictionary of values.
726 cur_obj = self.pool.get('res.currency')
727 for line in self.browse(cr, uid, ids, context=context):
728 res[line.id] = line.to_invoice and line.price_unit * line.product_uom_qty or 0
729 cur = line.repair_id.pricelist_id.currency_id
730 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
734 'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference', required=True, ondelete='cascade', select=True),
735 'name': fields.char('Description', size=64, select=True,required=True),
736 'product_id': fields.many2one('product.product', 'Product'),
737 'product_uom_qty': fields.float('Quantity', digits=(16,2), required=True),
738 'price_unit': fields.float('Unit Price', required=True),
739 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
740 'price_subtotal': fields.function(_amount_line, string='Subtotal',digits_compute= dp.get_precision('Sale Price')),
741 'tax_id': fields.many2many('account.tax', 'repair_fee_line_tax', 'repair_fee_line_id', 'tax_id', 'Taxes'),
742 'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True),
743 'to_invoice': fields.boolean('To Invoice'),
744 'invoiced': fields.boolean('Invoiced',readonly=True),
747 'to_invoice': lambda *a: True,
751 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: