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 openerp.tools.translate import _
25 import openerp.addons.decimal_precision as dp
27 class mrp_repair(osv.osv):
29 _inherit = 'mail.thread'
30 _description = 'Repair Order'
32 def _amount_untaxed(self, cr, uid, ids, field_name, arg, context=None):
33 """ Calculates untaxed amount.
34 @param self: The object pointer
35 @param cr: The current row, from the database cursor,
36 @param uid: The current user ID for security checks
37 @param ids: List of selected IDs
38 @param field_name: Name of field.
40 @param context: A standard dictionary for contextual values
41 @return: Dictionary of values.
44 cur_obj = self.pool.get('res.currency')
46 for repair in self.browse(cr, uid, ids, context=context):
48 for line in repair.operations:
49 res[repair.id] += line.price_subtotal
50 for line in repair.fees_lines:
51 res[repair.id] += line.price_subtotal
52 cur = repair.pricelist_id.currency_id
53 res[repair.id] = cur_obj.round(cr, uid, cur, res[repair.id])
56 def _amount_tax(self, cr, uid, ids, field_name, arg, context=None):
57 """ Calculates taxed amount.
58 @param field_name: Name of field.
60 @return: Dictionary of values.
63 #return {}.fromkeys(ids, 0)
64 cur_obj = self.pool.get('res.currency')
65 tax_obj = self.pool.get('account.tax')
66 for repair in self.browse(cr, uid, ids, context=context):
68 cur = repair.pricelist_id.currency_id
69 for line in repair.operations:
70 #manage prices with tax included use compute_all instead of compute
72 tax_calculate = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, line.product_uom_qty, line.product_id, repair.partner_id)
73 for c in tax_calculate['taxes']:
75 for line in repair.fees_lines:
77 tax_calculate = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, line.product_uom_qty, line.product_id, repair.partner_id)
78 for c in tax_calculate['taxes']:
80 res[repair.id] = cur_obj.round(cr, uid, cur, val)
83 def _amount_total(self, cr, uid, ids, field_name, arg, context=None):
84 """ Calculates total amount.
85 @param field_name: Name of field.
87 @return: Dictionary of values.
90 untax = self._amount_untaxed(cr, uid, ids, field_name, arg, context=context)
91 tax = self._amount_tax(cr, uid, ids, field_name, arg, context=context)
92 cur_obj = self.pool.get('res.currency')
94 repair = self.browse(cr, uid, id, context=context)
95 cur = repair.pricelist_id.currency_id
96 res[id] = cur_obj.round(cr, uid, cur, untax.get(id, 0.0) + tax.get(id, 0.0))
99 def _get_default_address(self, cr, uid, ids, field_name, arg, context=None):
101 partner_obj = self.pool.get('res.partner')
102 for data in self.browse(cr, uid, ids, context=context):
105 adr_id = partner_obj.address_get(cr, uid, [data.partner_id.id], ['default'])['default']
106 res[data.id] = adr_id
109 def _get_lines(self, cr, uid, ids, context=None):
110 return self.pool['mrp.repair'].search(cr, uid, [('operations', 'in', ids)], context=context)
112 def _get_fee_lines(self, cr, uid, ids, context=None):
113 return self.pool['mrp.repair'].search(cr, uid, [('fees_lines', 'in', ids)], context=context)
116 'name': fields.char('Repair Reference', size=24, required=True, states={'confirmed': [('readonly', True)]}),
117 'product_id': fields.many2one('product.product', string='Product to Repair', required=True, readonly=True, states={'draft': [('readonly', False)]}),
118 'product_qty': fields.float('Product Quantity', digits_compute=dp.get_precision('Product Unit of Measure'),
119 required=True, readonly=True, states={'draft': [('readonly', False)]}),
120 'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)]}),
121 '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)]}),
122 'address_id': fields.many2one('res.partner', 'Delivery Address', domain="[('parent_id','=',partner_id)]", states={'confirmed': [('readonly', True)]}),
123 'default_address_id': fields.function(_get_default_address, type="many2one", relation="res.partner"),
124 'state': fields.selection([
125 ('draft', 'Quotation'),
126 ('cancel', 'Cancelled'),
127 ('confirmed', 'Confirmed'),
128 ('under_repair', 'Under Repair'),
129 ('ready', 'Ready to Repair'),
130 ('2binvoiced', 'To be Invoiced'),
131 ('invoice_except', 'Invoice Exception'),
133 ], 'Status', readonly=True, track_visibility='onchange',
134 help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed repair order. \
135 \n* The \'Confirmed\' status is used when a user confirms the repair order. \
136 \n* The \'Ready to Repair\' status is used to start to repairing, user can start repairing only after repair order is confirmed. \
137 \n* The \'To be Invoiced\' status is used to generate the invoice before or after repairing done. \
138 \n* The \'Done\' status is set when repairing is completed.\
139 \n* The \'Cancelled\' status is used when user cancel repair order.'),
140 'location_id': fields.many2one('stock.location', 'Current Location', select=True, required=True, readonly=True, states={'draft': [('readonly', False)], 'confirmed': [('readonly', True)]}),
141 'location_dest_id': fields.many2one('stock.location', 'Delivery Location', readonly=True, required=True, states={'draft': [('readonly', False)], 'confirmed': [('readonly', True)]}),
142 'lot_id': fields.many2one('stock.production.lot', 'Repaired Lot', domain="[('product_id','=', product_id)]", help="Products repaired are all belonging to this lot"),
143 '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)]}),
144 'operations': fields.one2many('mrp.repair.line', 'repair_id', 'Operation Lines', readonly=True, states={'draft': [('readonly', False)]}),
145 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', help='Pricelist of the selected partner.'),
146 'partner_invoice_id': fields.many2one('res.partner', 'Invoicing Address'),
147 'invoice_method': fields.selection([
148 ("none", "No Invoice"),
149 ("b4repair", "Before Repair"),
150 ("after_repair", "After Repair")
152 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.'),
153 'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True, track_visibility="onchange"),
154 'move_id': fields.many2one('stock.move', 'Move', readonly=True, help="Move created by the repair order", track_visibility="onchange"),
155 'fees_lines': fields.one2many('mrp.repair.fee', 'repair_id', 'Fees Lines', readonly=True, states={'draft': [('readonly', False)]}),
156 'internal_notes': fields.text('Internal Notes'),
157 'quotation_notes': fields.text('Quotation Notes'),
158 'company_id': fields.many2one('res.company', 'Company'),
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', 'fees_lines'], 10),
164 'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
165 'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
167 'amount_tax': fields.function(_amount_tax, string='Taxes',
169 'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10),
170 'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
171 'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
173 'amount_total': fields.function(_amount_total, string='Total',
175 'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10),
176 'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
177 'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
181 def _default_stock_location(self, cr, uid, context=None):
183 warehouse = self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'warehouse0')
184 return warehouse.lot_stock_id.id
189 'state': lambda *a: 'draft',
190 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'mrp.repair'),
191 'invoice_method': lambda *a: 'none',
192 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.repair', context=context),
193 'pricelist_id': lambda self, cr, uid, context: self.pool.get('product.pricelist').search(cr, uid, [('type', '=', 'sale')])[0],
195 'location_id': _default_stock_location,
199 ('name', 'unique (name)', 'The name of the Repair Order must be unique!'),
202 def copy(self, cr, uid, id, default=None, context=None):
211 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.repair'),
213 return super(mrp_repair, self).copy(cr, uid, id, default, context)
215 def onchange_product_id(self, cr, uid, ids, product_id=None):
216 """ On change of product sets some values.
217 @param product_id: Changed product
218 @return: Dictionary of values.
222 product = self.pool.get("product.product").browse(cr, uid, product_id)
224 'guarantee_limit': False,
226 'product_uom': product and product.uom_id.id or False,
230 def onchange_product_uom(self, cr, uid, ids, product_id, product_uom, context=None):
232 if not product_uom or not product_id:
234 product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
235 uom = self.pool.get('product.uom').browse(cr, uid, product_uom, context=context)
236 if uom.category_id.id != product.uom_id.category_id.id:
237 res['warning'] = {'title': _('Warning'), 'message': _('The Product Unit of Measure you chose has a different category than in the product form.')}
238 res['value'].update({'product_uom': product.uom_id.id})
241 def onchange_location_id(self, cr, uid, ids, location_id=None):
242 """ On change of location
244 return {'value': {'location_dest_id': location_id}}
246 def button_dummy(self, cr, uid, ids, context=None):
249 def onchange_partner_id(self, cr, uid, ids, part, address_id):
250 """ On change of partner sets the values of partner address,
251 partner invoice address and pricelist.
252 @param part: Changed id of partner.
253 @param address_id: Address id from current record.
254 @return: Dictionary of values.
256 part_obj = self.pool.get('res.partner')
257 pricelist_obj = self.pool.get('product.pricelist')
261 'partner_invoice_id': False,
262 'pricelist_id': pricelist_obj.search(cr, uid, [('type', '=', 'sale')])[0]
265 addr = part_obj.address_get(cr, uid, [part], ['delivery', 'invoice', 'default'])
266 partner = part_obj.browse(cr, uid, part)
267 pricelist = partner.property_product_pricelist and partner.property_product_pricelist.id or False
269 'address_id': addr['delivery'] or addr['default'],
270 'partner_invoice_id': addr['invoice'],
271 'pricelist_id': pricelist
275 def action_cancel_draft(self, cr, uid, ids, *args):
276 """ Cancels repair order when it is in 'Draft' state.
277 @param *arg: Arguments
282 mrp_line_obj = self.pool.get('mrp.repair.line')
283 for repair in self.browse(cr, uid, ids):
284 mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'draft'})
285 self.write(cr, uid, ids, {'state': 'draft'})
286 return self.create_workflow(cr, uid, ids)
288 def action_confirm(self, cr, uid, ids, *args):
289 """ Repair order state is set to 'To be invoiced' when invoice method
290 is 'Before repair' else state becomes 'Confirmed'.
291 @param *arg: Arguments
294 mrp_line_obj = self.pool.get('mrp.repair.line')
295 for o in self.browse(cr, uid, ids):
296 if (o.invoice_method == 'b4repair'):
297 self.write(cr, uid, [o.id], {'state': '2binvoiced'})
299 self.write(cr, uid, [o.id], {'state': 'confirmed'})
300 for line in o.operations:
301 if line.product_id.track_production:
302 raise osv.except_osv(_('Warning!'), _("Serial number is required for operation line with product '%s'") % (line.product_id.name))
303 mrp_line_obj.write(cr, uid, [l.id for l in o.operations], {'state': 'confirmed'})
306 def action_cancel(self, cr, uid, ids, context=None):
307 """ Cancels repair order.
310 mrp_line_obj = self.pool.get('mrp.repair.line')
311 for repair in self.browse(cr, uid, ids, context=context):
312 if not repair.invoiced:
313 mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'cancel'}, context=context)
315 raise osv.except_osv(_('Warning!'), _('Repair order is already invoiced.'))
316 return self.write(cr, uid, ids, {'state': 'cancel'})
318 def wkf_invoice_create(self, cr, uid, ids, *args):
319 self.action_invoice_create(cr, uid, ids)
322 def action_invoice_create(self, cr, uid, ids, group=False, context=None):
323 """ Creates invoice(s) for repair order.
324 @param group: It is set to true when group invoice is to be generated.
325 @return: Invoice Ids.
329 inv_line_obj = self.pool.get('account.invoice.line')
330 inv_obj = self.pool.get('account.invoice')
331 repair_line_obj = self.pool.get('mrp.repair.line')
332 repair_fee_obj = self.pool.get('mrp.repair.fee')
333 for repair in self.browse(cr, uid, ids, context=context):
334 res[repair.id] = False
335 if repair.state in ('draft', 'cancel') or repair.invoice_id:
337 if not (repair.partner_id.id and repair.partner_invoice_id.id):
338 raise osv.except_osv(_('No partner!'), _('You have to select a Partner Invoice Address in the repair form!'))
339 comment = repair.quotation_notes
340 if (repair.invoice_method != 'none'):
341 if group and repair.partner_invoice_id.id in invoices_group:
342 inv_id = invoices_group[repair.partner_invoice_id.id]
343 invoice = inv_obj.browse(cr, uid, inv_id)
345 'name': invoice.name + ', ' + repair.name,
346 'origin': invoice.origin + ', ' + repair.name,
347 'comment': (comment and (invoice.comment and invoice.comment + "\n" + comment or comment)) or (invoice.comment and invoice.comment or ''),
349 inv_obj.write(cr, uid, [inv_id], invoice_vals, context=context)
351 if not repair.partner_id.property_account_receivable:
352 raise osv.except_osv(_('Error!'), _('No account defined for partner "%s".') % repair.partner_id.name)
353 account_id = repair.partner_id.property_account_receivable.id
356 'origin': repair.name,
357 'type': 'out_invoice',
358 'account_id': account_id,
359 'partner_id': repair.partner_id.id,
360 'currency_id': repair.pricelist_id.currency_id.id,
361 'comment': repair.quotation_notes,
362 'fiscal_position': repair.partner_id.property_account_position.id
364 inv_id = inv_obj.create(cr, uid, inv)
365 invoices_group[repair.partner_invoice_id.id] = inv_id
366 self.write(cr, uid, repair.id, {'invoiced': True, 'invoice_id': inv_id})
368 for operation in repair.operations:
369 if operation.to_invoice:
371 name = repair.name + '-' + operation.name
373 name = operation.name
375 if operation.product_id.property_account_income:
376 account_id = operation.product_id.property_account_income.id
377 elif operation.product_id.categ_id.property_account_income_categ:
378 account_id = operation.product_id.categ_id.property_account_income_categ.id
380 raise osv.except_osv(_('Error!'), _('No account defined for product "%s".') % operation.product_id.name)
382 invoice_line_id = inv_line_obj.create(cr, uid, {
383 'invoice_id': inv_id,
385 'origin': repair.name,
386 'account_id': account_id,
387 'quantity': operation.product_uom_qty,
388 'invoice_line_tax_id': [(6, 0, [x.id for x in operation.tax_id])],
389 'uos_id': operation.product_uom.id,
390 'price_unit': operation.price_unit,
391 'price_subtotal': operation.product_uom_qty * operation.price_unit,
392 'product_id': operation.product_id and operation.product_id.id or False
394 repair_line_obj.write(cr, uid, [operation.id], {'invoiced': True, 'invoice_line_id': invoice_line_id})
395 for fee in repair.fees_lines:
398 name = repair.name + '-' + fee.name
401 if not fee.product_id:
402 raise osv.except_osv(_('Warning!'), _('No product defined on Fees!'))
404 if fee.product_id.property_account_income:
405 account_id = fee.product_id.property_account_income.id
406 elif fee.product_id.categ_id.property_account_income_categ:
407 account_id = fee.product_id.categ_id.property_account_income_categ.id
409 raise osv.except_osv(_('Error!'), _('No account defined for product "%s".') % fee.product_id.name)
411 invoice_fee_id = inv_line_obj.create(cr, uid, {
412 'invoice_id': inv_id,
414 'origin': repair.name,
415 'account_id': account_id,
416 'quantity': fee.product_uom_qty,
417 'invoice_line_tax_id': [(6, 0, [x.id for x in fee.tax_id])],
418 'uos_id': fee.product_uom.id,
419 'product_id': fee.product_id and fee.product_id.id or False,
420 'price_unit': fee.price_unit,
421 'price_subtotal': fee.product_uom_qty * fee.price_unit
423 repair_fee_obj.write(cr, uid, [fee.id], {'invoiced': True, 'invoice_line_id': invoice_fee_id})
424 res[repair.id] = inv_id
427 def action_repair_ready(self, cr, uid, ids, context=None):
428 """ Writes repair order state to 'Ready'
431 for repair in self.browse(cr, uid, ids, context=context):
432 self.pool.get('mrp.repair.line').write(cr, uid, [l.id for
433 l in repair.operations], {'state': 'confirmed'}, context=context)
434 self.write(cr, uid, [repair.id], {'state': 'ready'})
437 def action_repair_start(self, cr, uid, ids, context=None):
438 """ Writes repair order state to 'Under Repair'
441 repair_line = self.pool.get('mrp.repair.line')
442 for repair in self.browse(cr, uid, ids, context=context):
443 repair_line.write(cr, uid, [l.id for
444 l in repair.operations], {'state': 'confirmed'}, context=context)
445 repair.write({'state': 'under_repair'})
448 def action_repair_end(self, cr, uid, ids, context=None):
449 """ Writes repair order state to 'To be invoiced' if invoice method is
450 After repair else state is set to 'Ready'.
453 for order in self.browse(cr, uid, ids, context=context):
455 val['repaired'] = True
456 if (not order.invoiced and order.invoice_method == 'after_repair'):
457 val['state'] = '2binvoiced'
458 elif (not order.invoiced and order.invoice_method == 'b4repair'):
459 val['state'] = 'ready'
462 self.write(cr, uid, [order.id], val)
465 def wkf_repair_done(self, cr, uid, ids, *args):
466 self.action_repair_done(cr, uid, ids)
469 def action_repair_done(self, cr, uid, ids, context=None):
470 """ Creates stock move for operation and stock move for final product of repair order.
471 @return: Move ids of final products
474 move_obj = self.pool.get('stock.move')
475 repair_line_obj = self.pool.get('mrp.repair.line')
476 for repair in self.browse(cr, uid, ids, context=context):
478 for move in repair.operations:
479 move_id = move_obj.create(cr, uid, {
481 'product_id': move.product_id.id,
482 'restrict_lot_id': move.lot_id.id,
483 'product_uom_qty': move.product_uom_qty,
484 'product_uom': move.product_uom.id,
485 'partner_id': repair.address_id and repair.address_id.id or False,
486 'location_id': move.location_id.id,
487 'location_dest_id': move.location_dest_id.id,
490 move_ids.append(move_id)
491 repair_line_obj.write(cr, uid, [move.id], {'move_id': move_id, 'state': 'done'}, context=context)
492 move_id = move_obj.create(cr, uid, {
494 'product_id': repair.product_id.id,
495 'product_uom': repair.product_uom.id or repair.product_id.uom_id.id,
496 'product_qty': repair.product_qty,
497 'partner_id': repair.address_id and repair.address_id.id or False,
498 'location_id': repair.location_id.id,
499 'location_dest_id': repair.location_dest_id.id,
500 'restrict_lot_id': repair.lot_id.id,
502 move_ids.append(move_id)
503 move_obj.action_done(cr, uid, move_ids, context=context)
504 self.write(cr, uid, [repair.id], {'state': 'done', 'move_id': move_id}, context=context)
505 res[repair.id] = move_id
509 class ProductChangeMixin(object):
510 def product_id_change(self, cr, uid, ids, pricelist, product, uom=False,
511 product_uom_qty=0, partner_id=False, guarantee_limit=False):
512 """ On change of product it sets product quantity, tax account, name,
513 uom of product, unit price and price subtotal.
514 @param pricelist: Pricelist of current record.
515 @param product: Changed id of product.
516 @param uom: UoM of current record.
517 @param product_uom_qty: Quantity of current record.
518 @param partner_id: Partner of current record.
519 @param guarantee_limit: Guarantee limit of current record.
520 @return: Dictionary of values and warning message.
525 if not product_uom_qty:
527 result['product_uom_qty'] = product_uom_qty
530 product_obj = self.pool.get('product.product').browse(cr, uid, product)
532 partner = self.pool.get('res.partner').browse(cr, uid, partner_id)
533 result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, partner.property_account_position, product_obj.taxes_id)
535 result['name'] = product_obj.partner_ref
536 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id or False
539 'title': _('No Pricelist!'),
541 _('You have to select a pricelist in the Repair form !\n'
542 'Please set one before choosing a product.')
545 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
546 product, product_uom_qty, partner_id, {'uom': uom})[pricelist]
550 'title': _('No valid pricelist line found !'),
552 _("Couldn't find a pricelist line matching this product and quantity.\n"
553 "You have to change either the product, the quantity or the pricelist.")
556 result.update({'price_unit': price, 'price_subtotal': price * product_uom_qty})
558 return {'value': result, 'warning': warning}
561 class mrp_repair_line(osv.osv, ProductChangeMixin):
562 _name = 'mrp.repair.line'
563 _description = 'Repair Line'
565 def copy_data(self, cr, uid, id, default=None, context=None):
568 default.update({'invoice_line_id': False, 'move_id': False, 'invoiced': False, 'state': 'draft'})
569 return super(mrp_repair_line, self).copy_data(cr, uid, id, default, context)
571 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
572 """ Calculates amount.
573 @param field_name: Name of field.
575 @return: Dictionary of values.
578 cur_obj = self.pool.get('res.currency')
579 for line in self.browse(cr, uid, ids, context=context):
580 res[line.id] = line.to_invoice and line.price_unit * line.product_uom_qty or 0
581 cur = line.repair_id.pricelist_id.currency_id
582 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
586 'name': fields.char('Description', size=64, required=True),
587 'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference', ondelete='cascade', select=True),
588 'type': fields.selection([('add', 'Add'), ('remove', 'Remove')], 'Type', required=True),
589 'to_invoice': fields.boolean('To Invoice'),
590 'product_id': fields.many2one('product.product', 'Product', required=True),
591 'invoiced': fields.boolean('Invoiced', readonly=True),
592 'price_unit': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Product Price')),
593 'price_subtotal': fields.function(_amount_line, string='Subtotal', digits_compute=dp.get_precision('Account')),
594 'tax_id': fields.many2many('account.tax', 'repair_operation_line_tax', 'repair_operation_line_id', 'tax_id', 'Taxes'),
595 'product_uom_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
596 'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
597 'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True),
598 'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True),
599 'location_dest_id': fields.many2one('stock.location', 'Dest. Location', required=True, select=True),
600 'move_id': fields.many2one('stock.move', 'Inventory Move', readonly=True),
601 'lot_id': fields.many2one('stock.production.lot', 'Lot'),
602 'state': fields.selection([
604 ('confirmed', 'Confirmed'),
606 ('cancel', 'Cancelled')], 'Status', required=True, readonly=True,
607 help=' * The \'Draft\' status is set automatically as draft when repair order in draft status. \
608 \n* The \'Confirmed\' status is set automatically as confirm when repair order in confirm status. \
609 \n* The \'Done\' status is set automatically when repair order is completed.\
610 \n* The \'Cancelled\' status is set automatically when user cancel repair order.'),
613 'state': lambda *a: 'draft',
614 'product_uom_qty': lambda *a: 1,
617 def onchange_operation_type(self, cr, uid, ids, type, guarantee_limit, company_id=False, context=None):
618 """ On change of operation type it sets source location, destination location
619 and to invoice field.
620 @param product: Changed operation type.
621 @param guarantee_limit: Guarantee limit of current record.
622 @return: Dictionary of values.
626 'location_id': False,
627 'location_dest_id': False
629 location_obj = self.pool.get('stock.location')
630 warehouse_obj = self.pool.get('stock.warehouse')
631 location_id = location_obj.search(cr, uid, [('usage', '=', 'production')], context=context)
632 location_id = location_id and location_id[0] or False
635 # TOCHECK: Find stock location for user's company warehouse or
636 # repair order's company's warehouse (company_id field is added in fix of lp:831583)
637 args = company_id and [('company_id', '=', company_id)] or []
638 warehouse_ids = warehouse_obj.search(cr, uid, args, context=context)
641 stock_id = warehouse_obj.browse(cr, uid, warehouse_ids[0], context=context).lot_stock_id.id
642 to_invoice = (guarantee_limit and datetime.strptime(guarantee_limit, '%Y-%m-%d') < datetime.now())
645 'to_invoice': to_invoice,
646 'location_id': stock_id,
647 'location_dest_id': location_id
649 scrap_location_ids = location_obj.search(cr, uid, [('scrap_location', '=', True)], context=context)
653 'location_id': location_id,
654 'location_dest_id': scrap_location_ids and scrap_location_ids[0] or False,
658 class mrp_repair_fee(osv.osv, ProductChangeMixin):
659 _name = 'mrp.repair.fee'
660 _description = 'Repair Fees Line'
662 def copy_data(self, cr, uid, id, default=None, context=None):
665 default.update({'invoice_line_id': False, 'invoiced': False})
666 return super(mrp_repair_fee, self).copy_data(cr, uid, id, default, context)
668 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
669 """ Calculates amount.
670 @param field_name: Name of field.
672 @return: Dictionary of values.
675 cur_obj = self.pool.get('res.currency')
676 for line in self.browse(cr, uid, ids, context=context):
677 res[line.id] = line.to_invoice and line.price_unit * line.product_uom_qty or 0
678 cur = line.repair_id.pricelist_id.currency_id
679 res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
683 'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference', required=True, ondelete='cascade', select=True),
684 'name': fields.char('Description', size=64, select=True, required=True),
685 'product_id': fields.many2one('product.product', 'Product'),
686 'product_uom_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
687 'price_unit': fields.float('Unit Price', required=True),
688 'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
689 'price_subtotal': fields.function(_amount_line, string='Subtotal', digits_compute=dp.get_precision('Account')),
690 'tax_id': fields.many2many('account.tax', 'repair_fee_line_tax', 'repair_fee_line_id', 'tax_id', 'Taxes'),
691 'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True),
692 'to_invoice': fields.boolean('To Invoice'),
693 'invoiced': fields.boolean('Invoiced', readonly=True),
697 'to_invoice': lambda *a: True,
700 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: