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:
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=None):
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=context)
89 tax = self._amount_tax(cr, uid, ids, field_name, arg, context=context)
90 cur_obj = self.pool.get('res.currency')
92 repair = self.browse(cr, uid, id, context=context)
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=None):
99 partner_obj = self.pool.get('res.partner')
100 for data in self.browse(cr, uid, ids, context=context):
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 limit = datetime.strptime(move.date_expected, '%Y-%m-%d %H:%M:%S') + relativedelta(months=int(product.warranty))
226 data['value']['guarantee_limit'] = limit.strftime('%Y-%m-%d')
227 data['value']['location_id'] = move.location_dest_id.id
228 data['value']['location_dest_id'] = move.location_dest_id.id
230 data['value']['partner_id'] = move.address_id.partner_id and move.address_id.partner_id.id
232 data['value']['partner_id'] = False
233 data['value']['address_id'] = move.address_id and move.address_id.id
234 d = self.onchange_partner_id(cr, uid, ids, data['value']['partner_id'], data['value']['address_id'])
235 data['value'].update(d['value'])
238 def button_dummy(self, cr, uid, ids, context=None):
241 def onchange_partner_id(self, cr, uid, ids, part, address_id):
242 """ On change of partner sets the values of partner address,
243 partner invoice address and pricelist.
244 @param part: Changed id of partner.
245 @param address_id: Address id from current record.
246 @return: Dictionary of values.
248 part_obj = self.pool.get('res.partner')
249 pricelist_obj = self.pool.get('product.pricelist')
253 'partner_invoice_id': False,
254 'pricelist_id': pricelist_obj.search(cr, uid, [('type','=','sale')])[0]
257 addr = part_obj.address_get(cr, uid, [part], ['delivery', 'invoice', 'default'])
258 partner = part_obj.browse(cr, uid, part)
259 pricelist = partner.property_product_pricelist and partner.property_product_pricelist.id or False
261 'address_id': address_id or addr['delivery'],
262 'partner_invoice_id': addr['invoice'],
263 'pricelist_id': pricelist
267 def onchange_lot_id(self, cr, uid, ids, lot, product_id):
268 """ On change of production lot sets the values of source location,
269 destination location, move and guarantee limit.
270 @param lot: Changed id of production lot.
271 @param product_id: Product id from current record.
272 @return: Dictionary of values.
274 move_obj = self.pool.get('stock.move')
277 'location_id': False,
278 'location_dest_id': False,
280 'guarantee_limit': False
285 move_ids = move_obj.search(cr, uid, [('prodlot_id', '=', lot)])
287 if not len(move_ids):
290 def get_last_move(lst_move):
291 while lst_move.move_dest_id and lst_move.move_dest_id.state == 'done':
292 lst_move = lst_move.move_dest_id
295 move_id = move_ids[0]
296 move = get_last_move(move_obj.browse(cr, uid, move_id))
297 data['value']['move_id'] = move.id
298 d = self.onchange_move_id(cr, uid, ids, product_id, move.id)
299 data['value'].update(d['value'])
302 def action_cancel_draft(self, cr, uid, ids, *args):
303 """ Cancels repair order when it is in 'Draft' state.
304 @param *arg: Arguments
309 mrp_line_obj = self.pool.get('mrp.repair.line')
310 for repair in self.browse(cr, uid, ids):
311 mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'draft'})
312 self.write(cr, uid, ids, {'state':'draft'})
313 wf_service = netsvc.LocalService("workflow")
315 wf_service.trg_create(uid, 'mrp.repair', id, cr)
318 def action_confirm(self, cr, uid, ids, *args):
319 """ Repair order state is set to 'To be invoiced' when invoice method
320 is 'Before repair' else state becomes 'Confirmed'.
321 @param *arg: Arguments
324 mrp_line_obj = self.pool.get('mrp.repair.line')
325 for o in self.browse(cr, uid, ids):
326 if (o.invoice_method == 'b4repair'):
327 self.write(cr, uid, [o.id], {'state': '2binvoiced'})
329 self.write(cr, uid, [o.id], {'state': 'confirmed'})
330 for line in o.operations:
331 if line.product_id.track_production and not line.prodlot_id:
332 raise osv.except_osv(_('Warning'), _("Production lot is required for opration line with product '%s'") % (line.product_id.name))
333 mrp_line_obj.write(cr, uid, [l.id for l in o.operations], {'state': 'confirmed'})
336 def action_cancel(self, cr, uid, ids, context=None):
337 """ Cancels repair order.
340 mrp_line_obj = self.pool.get('mrp.repair.line')
341 for repair in self.browse(cr, uid, ids, context=context):
342 mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'cancel'}, context=context)
343 self.write(cr,uid,ids,{'state':'cancel'})
346 def wkf_invoice_create(self, cr, uid, ids, *args):
347 return self.action_invoice_create(cr, uid, ids)
349 def action_invoice_create(self, cr, uid, ids, group=False, context=None):
350 """ Creates invoice(s) for repair order.
351 @param group: It is set to true when group invoice is to be generated.
352 @return: Invoice Ids.
356 inv_line_obj = self.pool.get('account.invoice.line')
357 inv_obj = self.pool.get('account.invoice')
358 repair_line_obj = self.pool.get('mrp.repair.line')
359 repair_fee_obj = self.pool.get('mrp.repair.fee')
360 for repair in self.browse(cr, uid, ids, context=context):
361 res[repair.id] = False
362 if repair.state in ('draft','cancel') or repair.invoice_id:
364 if not (repair.partner_id.id and repair.partner_invoice_id.id):
365 raise osv.except_osv(_('No partner !'),_('You have to select a Partner Invoice Address in the repair form !'))
366 comment = repair.quotation_notes
367 if (repair.invoice_method != 'none'):
368 if group and repair.partner_invoice_id.id in invoices_group:
369 inv_id = invoices_group[repair.partner_invoice_id.id]
370 invoice = inv_obj.browse(cr, uid, inv_id)
372 'name': invoice.name +', '+repair.name,
373 'origin': invoice.origin+', '+repair.name,
374 'comment':(comment and (invoice.comment and invoice.comment+"\n"+comment or comment)) or (invoice.comment and invoice.comment or ''),
376 inv_obj.write(cr, uid, [inv_id], invoice_vals, context=context)
378 if not repair.partner_id.property_account_receivable:
379 raise osv.except_osv(_('Error !'), _('No account defined for partner "%s".') % repair.partner_id.name )
380 account_id = repair.partner_id.property_account_receivable.id
383 'origin':repair.name,
384 'type': 'out_invoice',
385 'account_id': account_id,
386 'partner_id': repair.partner_id.id,
387 'address_invoice_id': repair.address_id.id,
388 'currency_id': repair.pricelist_id.currency_id.id,
389 'comment': repair.quotation_notes,
390 'fiscal_position': repair.partner_id.property_account_position.id
392 inv_id = inv_obj.create(cr, uid, inv)
393 invoices_group[repair.partner_invoice_id.id] = inv_id
394 self.write(cr, uid, repair.id, {'invoiced': True, 'invoice_id': inv_id})
396 for operation in repair.operations:
397 if operation.to_invoice == True:
399 name = repair.name + '-' + operation.name
401 name = operation.name
403 if operation.product_id.property_account_income:
404 account_id = operation.product_id.property_account_income.id
405 elif operation.product_id.categ_id.property_account_income_categ:
406 account_id = operation.product_id.categ_id.property_account_income_categ.id
408 raise osv.except_osv(_('Error !'), _('No account defined for product "%s".') % operation.product_id.name )
410 invoice_line_id = inv_line_obj.create(cr, uid, {
411 'invoice_id': inv_id,
413 'origin': repair.name,
414 'account_id': account_id,
415 'quantity': operation.product_uom_qty,
416 'invoice_line_tax_id': [(6,0,[x.id for x in operation.tax_id])],
417 'uos_id': operation.product_uom.id,
418 'price_unit': operation.price_unit,
419 'price_subtotal': operation.product_uom_qty*operation.price_unit,
420 'product_id': operation.product_id and operation.product_id.id or False
422 repair_line_obj.write(cr, uid, [operation.id], {'invoiced': True, 'invoice_line_id': invoice_line_id})
423 for fee in repair.fees_lines:
424 if fee.to_invoice == True:
426 name = repair.name + '-' + fee.name
429 if not fee.product_id:
430 raise osv.except_osv(_('Warning !'), _('No product defined on Fees!'))
432 if fee.product_id.property_account_income:
433 account_id = fee.product_id.property_account_income.id
434 elif fee.product_id.categ_id.property_account_income_categ:
435 account_id = fee.product_id.categ_id.property_account_income_categ.id
437 raise osv.except_osv(_('Error !'), _('No account defined for product "%s".') % fee.product_id.name)
439 invoice_fee_id = inv_line_obj.create(cr, uid, {
440 'invoice_id': inv_id,
442 'origin': repair.name,
443 'account_id': account_id,
444 'quantity': fee.product_uom_qty,
445 'invoice_line_tax_id': [(6,0,[x.id for x in fee.tax_id])],
446 'uos_id': fee.product_uom.id,
447 'product_id': fee.product_id and fee.product_id.id or False,
448 'price_unit': fee.price_unit,
449 'price_subtotal': fee.product_uom_qty*fee.price_unit
451 repair_fee_obj.write(cr, uid, [fee.id], {'invoiced': True, 'invoice_line_id': invoice_fee_id})
452 res[repair.id] = inv_id
455 def action_repair_ready(self, cr, uid, ids, context=None):
456 """ Writes repair order state to 'Ready'
459 for repair in self.browse(cr, uid, ids, context=context):
460 self.pool.get('mrp.repair.line').write(cr, uid, [l.id for
461 l in repair.operations], {'state': 'confirmed'}, context=context)
462 self.write(cr, uid, [repair.id], {'state': 'ready'})
465 def action_invoice_cancel(self, cr, uid, ids, context=None):
466 """ Writes repair order state to 'Exception in invoice'
469 self.write(cr, uid, ids, {'state': 'invoice_except'})
472 def action_repair_start(self, cr, uid, ids, context=None):
473 """ Writes repair order state to 'Under Repair'
476 repair_line = self.pool.get('mrp.repair.line')
477 for repair in self.browse(cr, uid, ids, context=context):
478 repair_line.write(cr, uid, [l.id for
479 l in repair.operations], {'state': 'confirmed'}, context=context)
480 repair.write({'state': 'under_repair'})
483 def action_invoice_end(self, cr, uid, ids, context=None):
484 """ Writes repair order state to 'Ready' if invoice method is Before repair.
487 repair_line = self.pool.get('mrp.repair.line')
488 for order in self.browse(cr, uid, ids, context=context):
490 if (order.invoice_method == 'b4repair'):
491 val['state'] = 'ready'
492 repair_line.write(cr, uid, [l.id for
493 l in order.operations], {'state': 'confirmed'}, context=context)
496 self.write(cr, uid, [order.id], val, context=context)
499 def action_repair_end(self, cr, uid, ids, context=None):
500 """ Writes repair order state to 'To be invoiced' if invoice method is
501 After repair else state is set to 'Ready'.
504 for order in self.browse(cr, uid, ids, context=context):
506 val['repaired'] = True
507 if (not order.invoiced and order.invoice_method=='after_repair'):
508 val['state'] = '2binvoiced'
509 elif (not order.invoiced and order.invoice_method=='b4repair'):
510 val['state'] = 'ready'
513 self.write(cr, uid, [order.id], val)
516 def wkf_repair_done(self, cr, uid, ids, *args):
517 self.action_repair_done(cr, uid, ids)
520 def action_repair_done(self, cr, uid, ids, context=None):
521 """ Creates stock move and picking for repair order.
522 @return: Picking ids.
525 move_obj = self.pool.get('stock.move')
526 wf_service = netsvc.LocalService("workflow")
527 repair_line_obj = self.pool.get('mrp.repair.line')
528 seq_obj = self.pool.get('ir.sequence')
529 pick_obj = self.pool.get('stock.picking')
530 for repair in self.browse(cr, uid, ids, context=context):
531 for move in repair.operations:
532 move_id = move_obj.create(cr, uid, {
534 'product_id': move.product_id.id,
535 'product_qty': move.product_uom_qty,
536 'product_uom': move.product_uom.id,
537 'address_id': repair.address_id and repair.address_id.id or False,
538 'location_id': move.location_id.id,
539 'location_dest_id': move.location_dest_id.id,
540 'tracking_id': False,
541 'prodlot_id': move.prodlot_id and move.prodlot_id.id or False,
544 repair_line_obj.write(cr, uid, [move.id], {'move_id': move_id, 'state': 'done'}, context=context)
545 if repair.deliver_bool:
546 pick_name = seq_obj.get(cr, uid, 'stock.picking.out')
547 picking = pick_obj.create(cr, uid, {
549 'origin': repair.name,
552 'address_id': repair.address_id and repair.address_id.id or False,
553 'note': repair.internal_notes,
554 'invoice_state': 'none',
557 move_id = move_obj.create(cr, uid, {
559 'picking_id': picking,
560 'product_id': repair.product_id.id,
562 'product_uom': repair.product_id.uom_id.id,
563 'prodlot_id': repair.prodlot_id and repair.prodlot_id.id or False,
564 'address_id': repair.address_id and repair.address_id.id or False,
565 'location_id': repair.location_id.id,
566 'location_dest_id': repair.location_dest_id.id,
567 'tracking_id': False,
570 wf_service.trg_validate(uid, 'stock.picking', picking, 'button_confirm', cr)
571 self.write(cr, uid, [repair.id], {'state': 'done', 'picking_id': picking})
572 res[repair.id] = picking
574 self.write(cr, uid, [repair.id], {'state': 'done'})
581 class ProductChangeMixin(object):
582 def product_id_change(self, cr, uid, ids, pricelist, product, uom=False,
583 product_uom_qty=0, partner_id=False, guarantee_limit=False):
584 """ On change of product it sets product quantity, tax account, name,
585 uom of product, unit price and price subtotal.
586 @param pricelist: Pricelist of current record.
587 @param product: Changed id of product.
588 @param uom: UoM of current record.
589 @param product_uom_qty: Quantity of current record.
590 @param partner_id: Partner of current record.
591 @param guarantee_limit: Guarantee limit of current record.
592 @return: Dictionary of values and warning message.
597 if not product_uom_qty:
599 result['product_uom_qty'] = product_uom_qty
602 product_obj = self.pool.get('product.product').browse(cr, uid, product)
604 partner = self.pool.get('res.partner').browse(cr, uid, partner_id)
605 result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, product_obj.taxes_id)
607 result['name'] = product_obj.partner_ref
608 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id or False
611 'title':'No Pricelist !',
613 'You have to select a pricelist in the Repair form !\n'
614 'Please set one before choosing a product.'
617 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
618 product, product_uom_qty, partner_id, {'uom': uom,})[pricelist]
622 'title':'No valid pricelist line found !',
624 "Couldn't find a pricelist line matching this product and quantity.\n"
625 "You have to change either the product, the quantity or the pricelist."
628 result.update({'price_unit': price, 'price_subtotal': price*product_uom_qty})
630 return {'value': result, 'warning': warning}
633 class mrp_repair_line(osv.osv, ProductChangeMixin):
634 _name = 'mrp.repair.line'
635 _description = 'Repair Line'
637 def copy_data(self, cr, uid, id, default=None, context=None):
638 if not default: default = {}
639 default.update( {'invoice_line_id': False, 'move_id': False, 'invoiced': False, 'state': 'draft'})
640 return super(mrp_repair_line, self).copy_data(cr, uid, id, default, context)
642 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
643 """ Calculates amount.
644 @param field_name: Name of field.
646 @return: Dictionary of values.
649 cur_obj=self.pool.get('res.currency')
650 for line in self.browse(cr, uid, ids, context=context):
651 res[line.id] = line.to_invoice and line.price_unit * line.product_uom_qty or 0
652 cur = line.repair_id.pricelist_id.currency_id
653 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
657 'name' : fields.char('Description',size=64,required=True),
658 'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference',ondelete='cascade', select=True),
659 'type': fields.selection([('add','Add'),('remove','Remove')],'Type', required=True),
660 'to_invoice': fields.boolean('To Invoice'),
661 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok','=',True)], required=True),
662 'invoiced': fields.boolean('Invoiced',readonly=True),
663 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Sale Price')),
664 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',digits_compute= dp.get_precision('Sale Price')),
665 'tax_id': fields.many2many('account.tax', 'repair_operation_line_tax', 'repair_operation_line_id', 'tax_id', 'Taxes'),
666 'product_uom_qty': fields.float('Quantity (UoM)', digits=(16,2), required=True),
667 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
668 'prodlot_id': fields.many2one('stock.production.lot', 'Lot Number',domain="[('product_id','=',product_id)]"),
669 'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True),
670 'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True),
671 'location_dest_id': fields.many2one('stock.location', 'Dest. Location', required=True, select=True),
672 'move_id': fields.many2one('stock.move', 'Inventory Move', readonly=True),
673 'state': fields.selection([
675 ('confirmed','Confirmed'),
677 ('cancel','Canceled')], 'State', required=True, readonly=True,
678 help=' * The \'Draft\' state is set automatically as draft when repair order in draft state. \
679 \n* The \'Confirmed\' state is set automatically as confirm when repair order in confirm state. \
680 \n* The \'Done\' state is set automatically when repair order is completed.\
681 \n* The \'Cancelled\' state is set automatically when user cancel repair order.'),
684 'state': lambda *a: 'draft',
685 'product_uom_qty': lambda *a: 1,
688 def onchange_operation_type(self, cr, uid, ids, type, guarantee_limit):
689 """ On change of operation type it sets source location, destination location
690 and to invoice field.
691 @param product: Changed operation type.
692 @param guarantee_limit: Guarantee limit of current record.
693 @return: Dictionary of values.
697 'location_id': False,
698 'location_dest_id': False
702 product_id = self.pool.get('stock.location').search(cr, uid, [('name','=','Production')])[0]
706 'location_id': product_id,
707 'location_dest_id': False
711 stock_id = self.pool.get('stock.location').search(cr, uid, [('name','=','Stock')])[0]
712 to_invoice = (guarantee_limit and
713 datetime.strptime(guarantee_limit, '%Y-%m-%d') < datetime.now())
715 'to_invoice': to_invoice,
716 'location_id': stock_id,
717 'location_dest_id': product_id
723 class mrp_repair_fee(osv.osv, ProductChangeMixin):
724 _name = 'mrp.repair.fee'
725 _description = 'Repair Fees Line'
727 def copy_data(self, cr, uid, id, default=None, context=None):
728 if not default: default = {}
729 default.update({'invoice_line_id': False, 'invoiced': False})
730 return super(mrp_repair_fee, self).copy_data(cr, uid, id, default, context)
732 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
733 """ Calculates amount.
734 @param field_name: Name of field.
736 @return: Dictionary of values.
739 cur_obj = self.pool.get('res.currency')
740 for line in self.browse(cr, uid, ids, context=context):
741 res[line.id] = line.to_invoice and line.price_unit * line.product_uom_qty or 0
742 cur = line.repair_id.pricelist_id.currency_id
743 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
747 'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference', required=True, ondelete='cascade', select=True),
748 'name': fields.char('Description', size=64, select=True,required=True),
749 'product_id': fields.many2one('product.product', 'Product'),
750 'product_uom_qty': fields.float('Quantity', digits=(16,2), required=True),
751 'price_unit': fields.float('Unit Price', required=True),
752 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
753 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',digits_compute= dp.get_precision('Sale Price')),
754 'tax_id': fields.many2many('account.tax', 'repair_fee_line_tax', 'repair_fee_line_id', 'tax_id', 'Taxes'),
755 'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True),
756 'to_invoice': fields.boolean('To Invoice'),
757 'invoiced': fields.boolean('Invoiced',readonly=True),
760 'to_invoice': lambda *a: True,
764 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: