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 openerp.osv import fields,osv
23 from datetime import datetime
24 from dateutil.relativedelta import relativedelta
25 from openerp.tools.translate import _
26 import openerp.addons.decimal_precision as dp
28 class mrp_repair(osv.osv):
30 _inherit = 'mail.thread'
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, 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, 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, states={'confirmed':[('readonly',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='Choose partner for whom the order will be invoiced and delivered.', states={'confirmed':[('readonly',True)]}),
120 'address_id': fields.many2one('res.partner', 'Delivery Address', domain="[('parent_id','=',partner_id)]", states={'confirmed':[('readonly',True)]}),
121 'default_address_id': fields.function(_get_default_address, type="many2one", relation="res.partner"),
122 'prodlot_id': fields.many2one('stock.production.lot', 'Lot Number', select=True, states={'draft':[('readonly',False)]},domain="[('product_id','=',product_id)]"),
123 'state': fields.selection([
124 ('draft','Quotation'),
125 ('cancel','Cancelled'),
126 ('confirmed','Confirmed'),
127 ('under_repair','Under Repair'),
128 ('ready','Ready to Repair'),
129 ('2binvoiced','To be Invoiced'),
130 ('invoice_except','Invoice Exception'),
132 ], 'Status', readonly=True, track_visibility='onchange',
133 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed repair order. \
134 \n* The \'Confirmed\' status is used when a user confirms the repair order. \
135 \n* The \'Ready to Repair\' status is used to start to repairing, user can start repairing only after repair order is confirmed. \
136 \n* The \'To be Invoiced\' status is used to generate the invoice before or after repairing done. \
137 \n* The \'Done\' status is set when repairing is completed.\
138 \n* The \'Cancelled\' status is used when user cancel repair order.'),
139 'location_id': fields.many2one('stock.location', 'Current Location', select=True, readonly=True, states={'draft':[('readonly',False)], 'confirmed':[('readonly',True)]}),
140 'location_dest_id': fields.many2one('stock.location', 'Delivery Location', readonly=True, states={'draft':[('readonly',False)], 'confirmed':[('readonly',True)]}),
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('Warranty Expiration', help="The warranty expiration limit is computed as: last move date + warranty defined on selected product. If the current date is below the warranty expiration limit, each operation and fee you will add will be set as 'not to invoiced' by default. Note that you can change manually afterwards.", states={'confirmed':[('readonly',True)]}),
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='Pricelist of the selected partner.'),
145 'partner_invoice_id':fields.many2one('res.partner', 'Invoicing Address'),
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='Selecting \'Before Repair\' or \'After Repair\' will allow you to generate invoice before or after the repair is done respectively. \'No invoice\' means you don\'t want to generate invoice for this repair order.'),
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 and create a picking with selected product. Note that you can select the locations in the Info tab, if you have the extended view.", states={'confirmed':[('readonly',True)]}),
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.
222 data['value'] = {'guarantee_limit': False, 'location_id': False, 'prodlot_id': False, 'partner_id': False}
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
232 data['value']['prodlot_id'] = move.prodlot_id.id
234 data['value']['partner_id'] = move.partner_id.id
236 data['value']['partner_id'] = False
237 d = self.onchange_partner_id(cr, uid, ids, data['value']['partner_id'], data['value']['partner_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': addr['delivery'] or addr['default'],
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 Serial Number sets the values of source location,
272 destination location, move and guarantee limit.
273 @param lot: Changed id of Serial Number.
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 self.create_workflow(cr, uid, ids)
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 for line in o.operations:
332 if line.product_id.track_production and not line.prodlot_id:
333 raise osv.except_osv(_('Warning!'), _("Serial number is required for operation line with product '%s'") % (line.product_id.name))
334 mrp_line_obj.write(cr, uid, [l.id for l in o.operations], {'state': 'confirmed'})
337 def action_cancel(self, cr, uid, ids, context=None):
338 """ Cancels repair order.
341 mrp_line_obj = self.pool.get('mrp.repair.line')
342 for repair in self.browse(cr, uid, ids, context=context):
343 if not repair.invoiced:
344 mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'cancel'}, context=context)
346 raise osv.except_osv(_('Warning!'),_('Repair order is already invoiced.'))
347 return self.write(cr,uid,ids,{'state':'cancel'})
349 def wkf_invoice_create(self, cr, uid, ids, *args):
350 self.action_invoice_create(cr, uid, ids)
353 def action_invoice_create(self, cr, uid, ids, group=False, context=None):
354 """ Creates invoice(s) for repair order.
355 @param group: It is set to true when group invoice is to be generated.
356 @return: Invoice Ids.
360 inv_line_obj = self.pool.get('account.invoice.line')
361 inv_obj = self.pool.get('account.invoice')
362 repair_line_obj = self.pool.get('mrp.repair.line')
363 repair_fee_obj = self.pool.get('mrp.repair.fee')
364 for repair in self.browse(cr, uid, ids, context=context):
365 res[repair.id] = False
366 if repair.state in ('draft','cancel') or repair.invoice_id:
368 if not (repair.partner_id.id and repair.partner_invoice_id.id):
369 raise osv.except_osv(_('No partner!'),_('You have to select a Partner Invoice Address in the repair form!'))
370 comment = repair.quotation_notes
371 if (repair.invoice_method != 'none'):
372 if group and repair.partner_invoice_id.id in invoices_group:
373 inv_id = invoices_group[repair.partner_invoice_id.id]
374 invoice = inv_obj.browse(cr, uid, inv_id)
376 'name': invoice.name +', '+repair.name,
377 'origin': invoice.origin+', '+repair.name,
378 'comment':(comment and (invoice.comment and invoice.comment+"\n"+comment or comment)) or (invoice.comment and invoice.comment or ''),
380 inv_obj.write(cr, uid, [inv_id], invoice_vals, context=context)
382 if not repair.partner_id.property_account_receivable:
383 raise osv.except_osv(_('Error!'), _('No account defined for partner "%s".') % repair.partner_id.name )
384 account_id = repair.partner_id.property_account_receivable.id
387 'origin':repair.name,
388 'type': 'out_invoice',
389 'account_id': account_id,
390 'partner_id': repair.partner_id.id,
391 'currency_id': repair.pricelist_id.currency_id.id,
392 'comment': repair.quotation_notes,
393 'fiscal_position': repair.partner_id.property_account_position.id
395 inv_id = inv_obj.create(cr, uid, inv)
396 invoices_group[repair.partner_invoice_id.id] = inv_id
397 self.write(cr, uid, repair.id, {'invoiced': True, 'invoice_id': inv_id})
399 for operation in repair.operations:
400 if operation.to_invoice == True:
402 name = repair.name + '-' + operation.name
404 name = operation.name
406 if operation.product_id.property_account_income:
407 account_id = operation.product_id.property_account_income.id
408 elif operation.product_id.categ_id.property_account_income_categ:
409 account_id = operation.product_id.categ_id.property_account_income_categ.id
411 raise osv.except_osv(_('Error!'), _('No account defined for product "%s".') % operation.product_id.name )
413 invoice_line_id = inv_line_obj.create(cr, uid, {
414 'invoice_id': inv_id,
416 'origin': repair.name,
417 'account_id': account_id,
418 'quantity': operation.product_uom_qty,
419 'invoice_line_tax_id': [(6,0,[x.id for x in operation.tax_id])],
420 'uos_id': operation.product_uom.id,
421 'price_unit': operation.price_unit,
422 'price_subtotal': operation.product_uom_qty*operation.price_unit,
423 'product_id': operation.product_id and operation.product_id.id or False
425 repair_line_obj.write(cr, uid, [operation.id], {'invoiced': True, 'invoice_line_id': invoice_line_id})
426 for fee in repair.fees_lines:
427 if fee.to_invoice == True:
429 name = repair.name + '-' + fee.name
432 if not fee.product_id:
433 raise osv.except_osv(_('Warning!'), _('No product defined on Fees!'))
435 if fee.product_id.property_account_income:
436 account_id = fee.product_id.property_account_income.id
437 elif fee.product_id.categ_id.property_account_income_categ:
438 account_id = fee.product_id.categ_id.property_account_income_categ.id
440 raise osv.except_osv(_('Error!'), _('No account defined for product "%s".') % fee.product_id.name)
442 invoice_fee_id = inv_line_obj.create(cr, uid, {
443 'invoice_id': inv_id,
445 'origin': repair.name,
446 'account_id': account_id,
447 'quantity': fee.product_uom_qty,
448 'invoice_line_tax_id': [(6,0,[x.id for x in fee.tax_id])],
449 'uos_id': fee.product_uom.id,
450 'product_id': fee.product_id and fee.product_id.id or False,
451 'price_unit': fee.price_unit,
452 'price_subtotal': fee.product_uom_qty*fee.price_unit
454 repair_fee_obj.write(cr, uid, [fee.id], {'invoiced': True, 'invoice_line_id': invoice_fee_id})
455 res[repair.id] = inv_id
458 def action_repair_ready(self, cr, uid, ids, context=None):
459 """ Writes repair order state to 'Ready'
462 for repair in self.browse(cr, uid, ids, context=context):
463 self.pool.get('mrp.repair.line').write(cr, uid, [l.id for
464 l in repair.operations], {'state': 'confirmed'}, context=context)
465 self.write(cr, uid, [repair.id], {'state': 'ready'})
468 def action_repair_start(self, cr, uid, ids, context=None):
469 """ Writes repair order state to 'Under Repair'
472 repair_line = self.pool.get('mrp.repair.line')
473 for repair in self.browse(cr, uid, ids, context=context):
474 repair_line.write(cr, uid, [l.id for
475 l in repair.operations], {'state': 'confirmed'}, context=context)
476 repair.write({'state': 'under_repair'})
479 def action_repair_end(self, cr, uid, ids, context=None):
480 """ Writes repair order state to 'To be invoiced' if invoice method is
481 After repair else state is set to 'Ready'.
484 for order in self.browse(cr, uid, ids, context=context):
486 val['repaired'] = True
487 if (not order.invoiced and order.invoice_method=='after_repair'):
488 val['state'] = '2binvoiced'
489 elif (not order.invoiced and order.invoice_method=='b4repair'):
490 val['state'] = 'ready'
493 self.write(cr, uid, [order.id], val)
496 def wkf_repair_done(self, cr, uid, ids, *args):
497 self.action_repair_done(cr, uid, ids)
500 def action_repair_done(self, cr, uid, ids, context=None):
501 """ Creates stock move and picking for repair order.
502 @return: Picking ids.
505 move_obj = self.pool.get('stock.move')
506 repair_line_obj = self.pool.get('mrp.repair.line')
507 seq_obj = self.pool.get('ir.sequence')
508 pick_obj = self.pool.get('stock.picking')
509 for repair in self.browse(cr, uid, ids, context=context):
510 for move in repair.operations:
511 move_id = move_obj.create(cr, uid, {
513 'product_id': move.product_id.id,
514 'product_qty': move.product_uom_qty,
515 'product_uom': move.product_uom.id,
516 'partner_id': repair.address_id and repair.address_id.id or False,
517 'location_id': move.location_id.id,
518 'location_dest_id': move.location_dest_id.id,
519 'tracking_id': False,
520 'prodlot_id': move.prodlot_id and move.prodlot_id.id or False,
523 move_obj.action_done(cr, uid, [move_id], context=context)
524 repair_line_obj.write(cr, uid, [move.id], {'move_id': move_id, 'state': 'done'}, context=context)
525 if repair.deliver_bool:
526 pick_name = seq_obj.get(cr, uid, 'stock.picking.out')
527 picking = pick_obj.create(cr, uid, {
529 'origin': repair.name,
532 'partner_id': repair.address_id and repair.address_id.id or False,
533 'note': repair.internal_notes,
534 'invoice_state': 'none',
537 move_id = move_obj.create(cr, uid, {
539 'picking_id': picking,
540 'product_id': repair.product_id.id,
541 'product_uom': repair.product_id.uom_id.id,
542 'prodlot_id': repair.prodlot_id and repair.prodlot_id.id or False,
543 'partner_id': repair.address_id and repair.address_id.id or False,
544 'location_id': repair.location_id.id,
545 'location_dest_id': repair.location_dest_id.id,
546 'tracking_id': False,
549 pick_obj.signal_button_confirm(cr, uid, [picking])
550 self.write(cr, uid, [repair.id], {'state': 'done', 'picking_id': picking})
551 res[repair.id] = picking
553 self.write(cr, uid, [repair.id], {'state': 'done'})
557 class ProductChangeMixin(object):
558 def product_id_change(self, cr, uid, ids, pricelist, product, uom=False,
559 product_uom_qty=0, partner_id=False, guarantee_limit=False):
560 """ On change of product it sets product quantity, tax account, name,
561 uom of product, unit price and price subtotal.
562 @param pricelist: Pricelist of current record.
563 @param product: Changed id of product.
564 @param uom: UoM of current record.
565 @param product_uom_qty: Quantity of current record.
566 @param partner_id: Partner of current record.
567 @param guarantee_limit: Guarantee limit of current record.
568 @return: Dictionary of values and warning message.
573 if not product_uom_qty:
575 result['product_uom_qty'] = product_uom_qty
578 product_obj = self.pool.get('product.product').browse(cr, uid, product)
580 partner = self.pool.get('res.partner').browse(cr, uid, partner_id)
581 result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, product_obj.taxes_id)
583 result['name'] = product_obj.partner_ref
584 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id or False
587 'title':'No Pricelist!',
589 'You have to select a pricelist in the Repair form !\n'
590 'Please set one before choosing a product.'
593 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
594 product, product_uom_qty, partner_id, {'uom': uom,})[pricelist]
598 'title':'No valid pricelist line found !',
600 "Couldn't find a pricelist line matching this product and quantity.\n"
601 "You have to change either the product, the quantity or the pricelist."
604 result.update({'price_unit': price, 'price_subtotal': price*product_uom_qty})
606 return {'value': result, 'warning': warning}
609 class mrp_repair_line(osv.osv, ProductChangeMixin):
610 _name = 'mrp.repair.line'
611 _description = 'Repair Line'
613 def copy_data(self, cr, uid, id, default=None, context=None):
614 if not default: default = {}
615 default.update( {'invoice_line_id': False, 'move_id': False, 'invoiced': False, 'state': 'draft'})
616 return super(mrp_repair_line, self).copy_data(cr, uid, id, default, context)
618 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
619 """ Calculates amount.
620 @param field_name: Name of field.
622 @return: Dictionary of values.
625 cur_obj=self.pool.get('res.currency')
626 for line in self.browse(cr, uid, ids, context=context):
627 res[line.id] = line.to_invoice and line.price_unit * line.product_uom_qty or 0
628 cur = line.repair_id.pricelist_id.currency_id
629 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
633 'name' : fields.char('Description',size=64,required=True),
634 'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference',ondelete='cascade', select=True),
635 'type': fields.selection([('add','Add'),('remove','Remove')],'Type', required=True),
636 'to_invoice': fields.boolean('To Invoice'),
637 'product_id': fields.many2one('product.product', 'Product', required=True),
638 'invoiced': fields.boolean('Invoiced',readonly=True),
639 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
640 'price_subtotal': fields.function(_amount_line, string='Subtotal',digits_compute= dp.get_precision('Account')),
641 'tax_id': fields.many2many('account.tax', 'repair_operation_line_tax', 'repair_operation_line_id', 'tax_id', 'Taxes'),
642 'product_uom_qty': fields.float('Quantity', digits_compute= dp.get_precision('Product Unit of Measure'), required=True),
643 'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
644 'prodlot_id': fields.many2one('stock.production.lot', 'Lot Number',domain="[('product_id','=',product_id)]"),
645 'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True),
646 'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True),
647 'location_dest_id': fields.many2one('stock.location', 'Dest. Location', required=True, select=True),
648 'move_id': fields.many2one('stock.move', 'Inventory Move', readonly=True),
649 'state': fields.selection([
651 ('confirmed','Confirmed'),
653 ('cancel','Cancelled')], 'Status', required=True, readonly=True,
654 help=' * The \'Draft\' status is set automatically as draft when repair order in draft status. \
655 \n* The \'Confirmed\' status is set automatically as confirm when repair order in confirm status. \
656 \n* The \'Done\' status is set automatically when repair order is completed.\
657 \n* The \'Cancelled\' status is set automatically when user cancel repair order.'),
660 'state': lambda *a: 'draft',
661 'product_uom_qty': lambda *a: 1,
664 def onchange_operation_type(self, cr, uid, ids, type, guarantee_limit, company_id=False, context=None):
665 """ On change of operation type it sets source location, destination location
666 and to invoice field.
667 @param product: Changed operation type.
668 @param guarantee_limit: Guarantee limit of current record.
669 @return: Dictionary of values.
673 'location_id': False,
674 'location_dest_id': False
676 location_obj = self.pool.get('stock.location')
677 warehouse_obj = self.pool.get('stock.warehouse')
678 location_id = location_obj.search(cr, uid, [('usage','=','production')], context=context)
679 location_id = location_id and location_id[0] or False
682 # TOCHECK: Find stock location for user's company warehouse or
683 # repair order's company's warehouse (company_id field is added in fix of lp:831583)
684 args = company_id and [('company_id', '=', company_id)] or []
685 warehouse_ids = warehouse_obj.search(cr, uid, args, context=context)
688 stock_id = warehouse_obj.browse(cr, uid, warehouse_ids[0], context=context).lot_stock_id.id
689 to_invoice = (guarantee_limit and datetime.strptime(guarantee_limit, '%Y-%m-%d') < datetime.now())
692 'to_invoice': to_invoice,
693 'location_id': stock_id,
694 'location_dest_id': location_id
696 scrap_location_ids = location_obj.search(cr, uid, [('scrap_location', '=', True)], context=context)
700 'location_id': location_id,
701 'location_dest_id': scrap_location_ids and scrap_location_ids[0] or False,
705 class mrp_repair_fee(osv.osv, ProductChangeMixin):
706 _name = 'mrp.repair.fee'
707 _description = 'Repair Fees Line'
709 def copy_data(self, cr, uid, id, default=None, context=None):
710 if not default: default = {}
711 default.update({'invoice_line_id': False, 'invoiced': False})
712 return super(mrp_repair_fee, self).copy_data(cr, uid, id, default, context)
714 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
715 """ Calculates amount.
716 @param field_name: Name of field.
718 @return: Dictionary of values.
721 cur_obj = self.pool.get('res.currency')
722 for line in self.browse(cr, uid, ids, context=context):
723 res[line.id] = line.to_invoice and line.price_unit * line.product_uom_qty or 0
724 cur = line.repair_id.pricelist_id.currency_id
725 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
729 'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference', required=True, ondelete='cascade', select=True),
730 'name': fields.char('Description', size=64, select=True,required=True),
731 'product_id': fields.many2one('product.product', 'Product'),
732 'product_uom_qty': fields.float('Quantity', digits_compute= dp.get_precision('Product Unit of Measure'), required=True),
733 'price_unit': fields.float('Unit Price', required=True),
734 'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
735 'price_subtotal': fields.function(_amount_line, string='Subtotal',digits_compute= dp.get_precision('Account')),
736 'tax_id': fields.many2many('account.tax', 'repair_fee_line_tax', 'repair_fee_line_id', 'tax_id', 'Taxes'),
737 'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True),
738 'to_invoice': fields.boolean('To Invoice'),
739 'invoiced': fields.boolean('Invoiced',readonly=True),
742 'to_invoice': lambda *a: True,
745 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: