diff --git a/crm_claim_rma/__openerp__.py b/crm_claim_rma/__openerp__.py index ded1de47..d1a7dc82 100644 --- a/crm_claim_rma/__openerp__.py +++ b/crm_claim_rma/__openerp__.py @@ -27,41 +27,49 @@ Management of Return Merchandise Authorization (RMA) ==================================================== -This module aim to improve the Claims by adding a way to manage the product returns. It -allows you to create and manage picking from a claim. It also introduce a new object -the claim lines to better handle that problematic. One Claim can have several lines that concern -the return of differents products. It's for every of them that you'll be able to check the -warranty (still running or not). +This module aims to improve the Claims by adding a way to manage the +product returns. It allows you to create and manage picking from a +claim. It also introduces a new object: the claim lines to better +handle that problematic. One Claim can have several lines that +concern the return of differents products. It's for every of them +that you'll be able to check the warranty (still running or not). -It mainly contain the following features: +It mainly contains the following features: * product returns (one by one, mass return by invoice) * warranty control & return address (based on invoice date and product form) * product picking in / out * product refund -* access to related customer data (orders, invoices, refunds, picking in/out) from a claim +* access to related customer data (orders, invoices, refunds, picking + in/out) from a claim -Using this module make the logistic flow of return this way: +Using this module makes the logistic flow of return this way: -* Returning product goes into Stock or Supplier location with a incoming shipment (depending - on the settings of the supplier info in the product form) +* Returning product goes into Stock or Supplier location with a incoming + shipment (depending on the settings of the supplier info in the + product form) * You can make a delivery from the RMA to send a new product to the Customer """, 'author': 'Akretion, Camptocamp', 'website': 'http://www.akretion.com, http://www.camptocamp.com', - 'depends': ['sale','stock','crm_claim','product_warranty'], - 'data': [ - 'wizard/claim_make_picking_view.xml', - 'crm_claim_rma_view.xml', - 'security/ir.model.access.csv', - 'account_invoice_view.xml', - 'stock_view.xml', - 'crm_claim_rma_data.xml', - ], - 'images': ['images/product_return.png', 'images/claim.png','images/return_line.png','images/exchange.png'], + 'depends': ['sale', + 'stock', + 'crm_claim', + 'product_warranty', + ], + 'data': ['wizard/claim_make_picking_view.xml', + 'crm_claim_rma_view.xml', + 'security/ir.model.access.csv', + 'account_invoice_view.xml', + 'stock_view.xml', + 'crm_claim_rma_data.xml', + ], + 'images': ['images/product_return.png', + 'images/claim.png', + 'images/return_line.png', + 'images/exchange.png', + ], 'installable': True, - 'active': False, + 'auto_install': False, } - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/crm_claim_rma/account_invoice.py b/crm_claim_rma/account_invoice.py index aeb0c1df..2d73d25c 100644 --- a/crm_claim_rma/account_invoice.py +++ b/crm_claim_rma/account_invoice.py @@ -19,7 +19,7 @@ # along with this program. If not, see . # ############################################################################## -from openerp.osv import fields, orm, osv +from openerp.osv import fields, orm from tools.translate import _ @@ -32,59 +32,60 @@ class account_invoice(orm.Model): } def _refund_cleanup_lines(self, cr, uid, lines, context=None): - """Override when from claim to update the quantity and link - to the claim line.""" - if context is None: context = {} - new_lines = [] - # check if is an invoice_line and we are from a claim - if context.get('claim_line_ids') and lines and lines[0]._name =='account.invoice.line' : - for claim_line_id in context.get('claim_line_ids'): - claim_info = self.pool.get('claim.line').read(cr, uid, - claim_line_id[1], - [ - 'invoice_line_id', - 'product_returned_quantity', - 'refund_line_id'], - context=context) - if not claim_info['refund_line_id']: - #For each lines replace quantity and add clain_line_id - inv_line_obj = self.pool.get('account.invoice.line') - inv_line = inv_line_obj.browse(cr, uid, - [claim_info['invoice_line_id'][0]], - context=context)[0] - clean_line = {} - for field in inv_line._all_columns.keys(): - column_type = inv_line._all_columns[field].column._type - if column_type == 'many2one': - clean_line[field] = inv_line[field].id - elif column_type not in ['many2many','one2many']: - clean_line[field] = inv_line[field] - elif field == 'invoice_line_tax_id': - tax_list = [] - for tax in inv_line[field]: - tax_list.append(tax.id) - clean_line[field] = [(6,0, tax_list)] - clean_line['quantity'] = claim_info['product_returned_quantity'] - clean_line['claim_line_id'] = [claim_line_id[1]] - new_lines.append(clean_line) - if not new_lines: - # TODO use custom states to show button of this wizard or - # not instead of raise an error - raise osv.except_osv(_('Error !'), - _('A refund has already been created for this claim !')) - else: - return super(account_invoice, self)._refund_cleanup_lines(cr, uid, lines, context=None) - return map(lambda x: (0,0,x), new_lines) - - def _prepare_refund(self, cr, uid, invoice, date=None, period_id=None, - description=None, journal_id=None, context=None): + """ Override when from claim to update the quantity and link to the + claim line.""" if context is None: - context={} - result = super(account_invoice, self)._prepare_refund(cr, uid, invoice, - date=date, period_id=period_id, description=description, + context = {} + new_lines = [] + inv_line_obj = self.pool.get('account.invoice.line') + claim_line_obj = self.pool.get('claim.line') + # check if is an invoice_line and we are from a claim + if not (context.get('claim_line_ids') and lines and + lines[0]._name =='account.invoice.line'): + return super(account_invoice, self)._refund_cleanup_lines( + cr, uid, lines, context=None) + + for __, claim_line_id in context.get('claim_line_ids'): + line = claim_line_obj.browse(cr, uid, claim_line_id, + context=context) + if not line.refund_line_id: + # For each lines replace quantity and add claim_line_id + inv_line = inv_line_obj.browse(cr, uid, + line.invoice_line_id.id, + context=context) + clean_line = {} + for field_name, field in inv_line._all_columns.iteritems(): + column_type = field.column._type + if column_type == 'many2one': + clean_line[field_name] = inv_line[field_name].id + elif column_type not in ('many2many', 'one2many'): + clean_line[field_name] = inv_line[field_name] + elif field_name == 'invoice_line_tax_id': + tax_list = [] + for tax in inv_line[field_name]: + tax_list.append(tax.id) + clean_line[field_name] = [(6, 0, tax_list)] + clean_line['quantity'] = line['product_returned_quantity'] + clean_line['claim_line_id'] = [claim_line_id] + new_lines.append(clean_line) + if not new_lines: + # TODO use custom states to show button of this wizard or + # not instead of raise an error + raise orm.except_orm( + _('Error !'), + _('A refund has already been created for this claim !')) + return [(0, 0, line) for line in new_lines] + + def _prepare_refund(self, cr, uid, invoice, date=None, period_id=None, + description=None, journal_id=None, context=None): + if context is None: + context = {} + result = super(account_invoice, self)._prepare_refund( + cr, uid, invoice, + date=date, period_id=period_id, description=description, journal_id=journal_id, context=context) if context.get('claim_id'): - result['claim_id'] = context.get('claim_id') + result['claim_id'] = context['claim_id'] return result @@ -97,10 +98,11 @@ class account_invoice_line(orm.Model): if vals.get('claim_line_id'): claim_line_id = vals['claim_line_id'] del vals['claim_line_id'] - line_id = super(account_invoice_line, self).create(cr, uid, - vals, context=context) + line_id = super(account_invoice_line, self).create( + cr, uid, vals, context=context) if claim_line_id: claim_line_obj = self.pool.get('claim.line') - claim_line_obj.write(cr, uid, claim_line_id, - {'refund_line_id': line_id}, context=context) + claim_line_obj.write(cr, uid, claim_line_id, + {'refund_line_id': line_id}, + context=context) return line_id diff --git a/crm_claim_rma/crm_claim_rma.py b/crm_claim_rma/crm_claim_rma.py index 13e4cfd1..f9423bc7 100644 --- a/crm_claim_rma/crm_claim_rma.py +++ b/crm_claim_rma/crm_claim_rma.py @@ -2,7 +2,7 @@ ############################################################################## # # Copyright 2013 Camptocamp -# Copyright 2009-2013 Akretion, +# Copyright 2009-2013 Akretion, # Author: Emmanuel Samyn, Raphaël Valyi, Sébastien Beau, Joel Grand-Guillaume # # This program is free software: you can redistribute it and/or modify @@ -20,28 +20,24 @@ # ############################################################################## -from openerp.osv import fields, orm, osv -# from crm import crm +import time +from openerp.osv import fields, orm from datetime import datetime from dateutil.relativedelta import relativedelta -import time -from tools.translate import _ -from tools import DEFAULT_SERVER_DATE_FORMAT -from tools.translate import _ +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT +from openerp.tools.translate import _ -class substate_substate(orm.Model): - """ - To precise a state (state=refused; substates= reason 1, 2,...) - """ +class substate_substate(orm.Model): + """ To precise a state (state=refused; substates= reason 1, 2,...) """ _name = "substate.substate" _description = "substate that precise a given state" _columns = { - 'name': fields.char('Sub state', size=128, required=True), - 'substate_descr' : fields.text('Description', - help="To give more information about the sub state"), - # ADD OBJECT TO FILTER - } + 'name': fields.char('Sub state', required=True), + 'substate_descr': fields.text( + 'Description', + help="To give more information about the sub state"), + } class claim_line(orm.Model): @@ -50,18 +46,19 @@ class claim_line(orm.Model): """ _name = "claim.line" _description = "List of product to return" - + # Comment written in a claim.line to know about the warranty status WARRANT_COMMENT = { 'valid': "Valid", 'expired': "Expired", 'not_define': "Not Defined"} - + # Method to calculate total amount of the line : qty*UP def _line_total_amount(self, cr, uid, ids, field_name, arg, context=None): res = {} - for line in self.browse(cr,uid,ids): - res[line.id] = line.unit_sale_price*line.product_returned_quantity + for line in self.browse(cr, uid, ids, context=context): + res[line.id] = (line.unit_sale_price * + line.product_returned_quantity) return res def copy_data(self, cr, uid, id, default=None, context=None): @@ -75,134 +72,158 @@ class claim_line(orm.Model): std_default.update(default) return super(claim_line, self).copy_data( cr, uid, id, default=std_default, context=context) - + def get_warranty_return_partner(self, cr, uid, context=None): seller = self.pool.get('product.supplierinfo') result = seller.get_warranty_return_partner(cr, uid, context=context) return result _columns = { - 'name': fields.char('Description', size=64,required=True), - 'claim_origine': fields.selection([('none','Not specified'), - ('legal','Legal retractation'), - ('cancellation','Order cancellation'), - ('damaged','Damaged delivered product'), - ('error','Shipping error'), - ('exchange','Exchange request'), - ('lost','Lost during transport'), - ('other','Other')], + 'name': fields.char('Description', required=True), + 'claim_origine': fields.selection( + [('none', 'Not specified'), + ('legal', 'Legal retractation'), + ('cancellation', 'Order cancellation'), + ('damaged', 'Damaged delivered product'), + ('error', 'Shipping error'), + ('exchange', 'Exchange request'), + ('lost', 'Lost during transport'), + ('other', 'Other') + ], 'Claim Subject', required=True, help="To describe the line product problem"), - 'claim_descr' : fields.text('Claim description', - help="More precise description of the problem"), - 'product_id': fields.many2one('product.product', 'Product', + 'claim_descr': fields.text( + 'Claim description', + help="More precise description of the problem"), + 'product_id': fields.many2one( + 'product.product', + string='Product', help="Returned product"), - 'product_returned_quantity' : fields.float('Quantity', digits=(12,2), + 'product_returned_quantity': fields.float( + 'Quantity', digits=(12, 2), help="Quantity of product returned"), - 'unit_sale_price' : fields.float('Unit sale price', digits=(12,2), - help="Unit sale price of the product. Auto filed if retrun done by" - " invoice selection. BE CAREFUL AND CHECK the automatic value " - "as don't take into account previous refounds, invoice " + 'unit_sale_price' : fields.float( + 'Unit sale price', digits=(12, 2), + help="Unit sale price of the product. Auto filled if retrun done " + "by invoice selection. Be careful and check the automatic " + "value as don't take into account previous refunds, invoice " "discount, can be for 0 if product for free,..."), - 'return_value' : fields.function(_line_total_amount, method=True, - string='Total return', - type='float', + 'return_value' : fields.function( + _line_total_amount, string='Total return', type='float', help="Quantity returned * Unit sold price",), - 'prodlot_id': fields.many2one('stock.production.lot', 'Serial/Lot n°', + 'prodlot_id': fields.many2one( + 'stock.production.lot', + string='Serial/Lot n°', help="The serial/lot of the returned product"), 'applicable_guarantee': fields.selection( - [ - ('us','Company'), - ('supplier','Supplier'), - ('brand','Brand manufacturer')], + [('us', 'Company'), + ('supplier', 'Supplier'), + ('brand', 'Brand manufacturer')], 'Warranty type'), - 'guarantee_limit': fields.date('Warranty limit', + 'guarantee_limit': fields.date( + 'Warranty limit', readonly=True, help="The warranty limit is computed as: invoice date + warranty " "defined on selected product."), - 'warning': fields.char('Warranty', size=64, + 'warning': fields.char( + 'Warranty', readonly=True, help="If warranty has expired"), - "warranty_type": fields.selection(get_warranty_return_partner, + "warranty_type": fields.selection( + get_warranty_return_partner, 'Warranty type', readonly=True, - help="Who is in charge of the warranty return treatment toward the end customer. " - "Company will use the current compagny delivery or default address and so on for " - "supplier and brand manufacturer. Doesn't necessarly mean that the warranty to be " - "applied is the one of the return partner (ie: can be returned to the company and " - "be under the brand warranty"), - "warranty_return_partner" : fields.many2one('res.partner', - 'Warranty Address', + help="Who is in charge of the warranty return treatment towards the end customer. " + "Company will use the current company delivery or default address and so on for " + "supplier and brand manufacturer. Does not necessarily mean that the warranty to be " + "applied is the one of the return partner (ie: can be returned to the company and " + "be under the brand warranty"), + "warranty_return_partner" : fields.many2one( + 'res.partner', + string='Warranty Address', help="Where the customer has to send back the product(s)"), - 'claim_id': fields.many2one('crm.claim', 'Related claim', + 'claim_id': fields.many2one( + 'crm.claim', string='Related claim', help="To link to the case.claim object"), - 'state' : fields.selection([('draft','Draft'), - ('refused','Refused'), - ('confirmed','Confirmed, waiting for product'), - ('in_to_control','Received, to control'), - ('in_to_treate','Controlled, to treate'), - ('treated','Treated')], 'State'), - 'substate_id': fields.many2one('substate.substate', 'Sub state', + 'state' : fields.selection( + [('draft', 'Draft'), + ('refused', 'Refused'), + ('confirmed', 'Confirmed, waiting for product'), + ('in_to_control', 'Received, to control'), + ('in_to_treate', 'Controlled, to treate'), + ('treated', 'Treated')], + string='State'), + 'substate_id': fields.many2one( + 'substate.substate', + string='Sub state', help="Select a sub state to precise the standard state. Example 1: " "state = refused; substate could be warranty over, not in " "warranty, no problem,... . Example 2: state = to treate; " "substate could be to refund, to exchange, to repair,..."), - 'last_state_change': fields.date('Last change', + 'last_state_change': fields.date( + string='Last change', help="To set the last state / substate change"), - 'invoice_line_id': fields.many2one('account.invoice.line', - 'Invoice Line', + 'invoice_line_id': fields.many2one( + 'account.invoice.line', + string='Invoice Line', help='The invoice line related to the returned product'), - 'refund_line_id': fields.many2one('account.invoice.line', - 'Refund Line', + 'refund_line_id': fields.many2one( + 'account.invoice.line', + string='Refund Line', help='The refund line related to the returned product'), - 'move_in_id': fields.many2one('stock.move', - 'Move Line from picking in', + 'move_in_id': fields.many2one( + 'stock.move', + string='Move Line from picking in', help='The move line related to the returned product'), - 'move_out_id': fields.many2one('stock.move', - 'Move Line from picking out', + 'move_out_id': fields.many2one( + 'stock.move', + string='Move Line from picking out', help='The move line related to the returned product'), - 'location_dest_id': fields.many2one('stock.location', - 'Return Stock Location', + 'location_dest_id': fields.many2one( + 'stock.location', + string='Return Stock Location', help='The return stock location of the returned product'), } _defaults = { - 'state': lambda *a: 'draft', - 'name': lambda *a: 'none', - } + 'state': 'draft', + 'name': 'none', + } # Method to calculate warranty limit def set_warranty_limit(self, cr, uid, ids, claim_line, context=None): date_invoice = claim_line.invoice_line_id.invoice_id.date_invoice - if date_invoice: - warning = _(self.WARRANT_COMMENT['not_define']) - date_inv_at_server = datetime.strptime(date_invoice, - DEFAULT_SERVER_DATE_FORMAT) - supplier = claim_line.product_id.seller_ids[0] - if claim_line.claim_id.claim_type == 'supplier': - waranty_duration = int(supplier.warranty_duration) - else: - waranty_duration = int(claim_line.product_id.warranty) - limit = (date_inv_at_server + - relativedelta(months=waranty_duration)).strftime(DEFAULT_SERVER_DATE_FORMAT) - # If waranty period was defined - if waranty_duration > 0: - if limit < claim_line.claim_id.date: - warning = _(self.WARRANT_COMMENT['expired']) - else: - warning = _(self.WARRANT_COMMENT['valid']) - self.write(cr,uid,ids,{ - 'guarantee_limit' : limit, - 'warning' : warning, - }) + if not date_invoice: + raise orm.except_orm( + _('Error !'), + _('Cannot find any date for invoice. ' + 'Must be a validated invoice.')) + warning = _(self.WARRANT_COMMENT['not_define']) + date_inv_at_server = datetime.strptime(date_invoice, + DEFAULT_SERVER_DATE_FORMAT) + supplier = claim_line.product_id.seller_ids[0] + if claim_line.claim_id.claim_type == 'supplier': + warranty_duration = int(supplier.warranty_duration) else: - raise osv.except_osv(_('Error !'), - _('Cannot find any date for invoice ! Must be a validated invoice !')) + warranty_duration = int(claim_line.product_id.warranty) + limit = date_inv_at_server + relativedelta(months=warranty_duration) + # If waranty period was defined + if warranty_duration > 0: + claim_date = datetime.strptime(claim_line.claim_id.date, + DEFAULT_SERVER_DATE_FORMAT) + if limit < claim_date: + warning = _(self.WARRANT_COMMENT['expired']) + else: + warning = _(self.WARRANT_COMMENT['valid']) + self.write(cr, uid, ids, + {'guarantee_limit': limit.strftime(DEFAULT_SERVER_DATE_FORMAT), + 'warning': warning}, + context=context) return True - def get_destination_location(self, cr, uid, product_id, - warehouse_id, context=None): + def get_destination_location(self, cr, uid, product_id, + warehouse_id, context=None): """Compute and return the destination location ID to take for a return. Always take 'Supplier' one when return type different from company.""" @@ -220,41 +241,44 @@ class claim_line(orm.Model): return location_dest_id # Method to calculate warranty return address - def set_warranty_return_address(self, cr, uid, ids, - claim_line, context=None): + def set_warranty_return_address(self, cr, uid, ids, claim_line, context=None): """Return the partner to be used as return destination and the destination stock location of the line in case of return. + We can have various case here: - company or other: return to company partner or crm_return_address_id if specified - - supplier: return to the supplier address""" + - supplier: return to the supplier address + + """ return_address = None seller = claim_line.product_id.seller_info_id claim_company = claim_line.claim_id.company_id return_address = seller.warranty_return_address.id return_type = seller.warranty_return_partner - location_dest_id = self.get_destination_location(cr, uid, - claim_line.product_id.id, + location_dest_id = self.get_destination_location( + cr, uid, claim_line.product_id.id, claim_line.claim_id.warehouse_id.id, context=context) self.write(cr, uid, ids, - {'warranty_return_partner': return_address, - 'warranty_type': return_type, - 'location_dest_id': location_dest_id}) + {'warranty_return_partner': return_address, + 'warranty_type': return_type, + 'location_dest_id': location_dest_id}, + context=context) return True - - # Method to calculate warranty limit and address + def set_warranty(self, cr, uid, ids, context=None): + """ Calculate warranty limit and address """ for claim_line in self.browse(cr, uid, ids, context=context): - if claim_line.product_id and claim_line.invoice_line_id: - self.set_warranty_limit(cr, uid, ids, - claim_line, context=context) - self.set_warranty_return_address(cr, uid, ids, - claim_line, context=context) - else: - raise osv.except_osv(_('Error !'), - _('PLEASE SET PRODUCT & INVOICE!')) - return True + if not (claim_line.product_id and claim_line.invoice_line_id): + raise orm.except_orm( + _('Error !'), + _('Please set product and invoice.')) + self.set_warranty_limit(cr, uid, ids, + claim_line, context=context) + self.set_warranty_return_address(cr, uid, ids, + claim_line, context=context) + return True #TODO add the option to split the claim_line in order to manage the same product separately @@ -262,18 +286,22 @@ class crm_claim(orm.Model): _inherit = 'crm.claim' def _get_sequence_number(self, cr, uid, context=None): - res = self.pool.get('ir.sequence').get(cr, uid, - 'crm.claim.rma', context=context) or '/' + seq_obj = self.pool.get('ir.sequence') + res = seq_obj.get(cr, uid, 'crm.claim.rma', context=context) or '/' return res def _get_default_warehouse(self, cr, uid, context=None): - company_id = self.pool.get('res.users').browse(cr, uid, uid, - context=context).company_id.id - wh_ids = self.pool.get('stock.warehouse').search(cr, uid, - [('company_id','=',company_id)], context=context) + user_obj = self.pool.get('res.users') + user = user_obj.browse(cr, uid, uid, context=context) + company_id = user.company_id.id + wh_obj = self.pool.get('stock.warehouse') + wh_ids = wh_obj.search(cr, uid, + [('company_id', '=', company_id)], + context=context) if not wh_ids: - raise osv.except_osv(_('Error!'), - _('There is no warehouse for the current user\'s company!')) + raise orm.except_orm( + _('Error!'), + _('There is no warehouse for the current user\'s company.')) return wh_ids[0] def name_get(self, cr, uid, ids, context=None): @@ -283,9 +311,9 @@ class crm_claim(orm.Model): return res def create(self, cr, uid, vals, context=None): - if ('number' not in vals) or (vals.get('number')=='/'): + if ('number' not in vals) or (vals.get('number') == '/'): vals['number'] = self._get_sequence_number(cr, uid, context=context) - new_id = super(crm_claim, self).create(cr, uid, vals, context) + new_id = super(crm_claim, self).create(cr, uid, vals, context=context) return new_id def copy_data(self, cr, uid, id, default=None, context=None): @@ -301,49 +329,55 @@ class crm_claim(orm.Model): cr, uid, id, default=std_default, context=context) _columns = { - 'number': fields.char('Number', readonly=True, + 'number': fields.char( + 'Number', readonly=True, states={'draft': [('readonly', False)]}, required=True, select=True, help="Company internal claim unique number"), - 'claim_type': fields.selection([('customer','Customer'), - ('supplier','Supplier'), - ('other','Other')], - 'Claim type', + 'claim_type': fields.selection( + [('customer', 'Customer'), + ('supplier', 'Supplier'), + ('other', 'Other')], + string='Claim type', required=True, - help="customer = from customer to company ; supplier = from " - "company to supplier"), - 'claim_line_ids' : fields.one2many('claim.line', 'claim_id', - 'Return lines'), + help="Customer: from customer to company.\n " + "Supplier: from company to supplier."), + 'claim_line_ids': fields.one2many( + 'claim.line', 'claim_id', + string='Return lines'), 'planned_revenue': fields.float('Expected revenue'), 'planned_cost': fields.float('Expected cost'), 'real_revenue': fields.float('Real revenue'), 'real_cost': fields.float('Real cost'), 'invoice_ids': fields.one2many('account.invoice', 'claim_id', 'Refunds'), 'picking_ids': fields.one2many('stock.picking', 'claim_id', 'RMA'), - 'invoice_id': fields.many2one('account.invoice', 'Invoice', + 'invoice_id': fields.many2one( + 'account.invoice', string='Invoice', help='Related original Cusotmer invoice'), - 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', + 'warehouse_id': fields.many2one( + 'stock.warehouse', string='Warehouse', required=True), } _defaults = { - 'number': lambda self, cr, uid, context: '/', + 'number': '/', 'claim_type': 'customer', 'warehouse_id': _get_default_warehouse, } _sql_constraints = [ - ('number_uniq', 'unique(number, company_id)', 'Number/Reference must be unique per Company!'), + ('number_uniq', 'unique(number, company_id)', + 'Number/Reference must be unique per Company!'), ] - def onchange_partner_address_id(self, cr, uid, ids, add, - email=False, context=None): - res = super(crm_claim, self).onchange_partner_address_id(cr, uid, ids, - add, email=email) + def onchange_partner_address_id(self, cr, uid, ids, add, email=False, context=None): + res = super(crm_claim, self).onchange_partner_address_id( + cr, uid, ids, add, email=email) if add: if not res['value']['email_from'] or not res['value']['partner_phone']: - address = self.pool.get('res.partner').browse(cr, uid, add) + partner_obj = self.pool.get('res.partner') + address = partner_obj.browse(cr, uid, add, context=context) for other_add in address.partner_id.address: if other_add.email and not res['value']['email_from']: res['value']['email_from'] = other_add.email @@ -351,29 +385,30 @@ class crm_claim(orm.Model): res['value']['partner_phone'] = other_add.phone return res - def onchange_invoice_id(self, cr, uid, ids, invoice_id, - warehouse_id, context=None): + def onchange_invoice_id(self, cr, uid, ids, invoice_id, warehouse_id, context=None): invoice_line_obj = self.pool.get('account.invoice.line') claim_line_obj = self.pool.get('claim.line') - invoice_line_ids = invoice_line_obj.search(cr, uid, - [('invoice_id','=',invoice_id)]) + invoice_line_ids = invoice_line_obj.search( + cr, uid, + [('invoice_id', '=', invoice_id)], + context=context) claim_lines = [] if not warehouse_id: warehouse_id = self._get_default_warehouse(cr, uid, context=context) - for invoice_line in invoice_line_obj.browse(cr,uid,invoice_line_ids): - location_dest_id = claim_line_obj.get_destination_location(cr, uid, - invoice_line.product_id.id, - warehouse_id, - context=context) + invoice_lines = invoice_line_obj.browse(cr, uid, invoice_line_ids, + context=context) + for invoice_line in invoice_lines: + location_dest_id = claim_line_obj.get_destination_location( + cr, uid, invoice_line.product_id.id, + warehouse_id, context=context) claim_lines.append({ - 'name': invoice_line.name, - 'claim_origine' : "none", - 'invoice_line_id': invoice_line.id, - 'product_id' : invoice_line.product_id.id, - 'product_returned_quantity' : invoice_line.quantity, - 'unit_sale_price' : invoice_line.price_unit, - 'location_dest_id': location_dest_id, - 'state' : 'draft', - }) - return {'value' : {'claim_line_ids' : claim_lines}} - + 'name': invoice_line.name, + 'claim_origine': "none", + 'invoice_line_id': invoice_line.id, + 'product_id': invoice_line.product_id.id, + 'product_returned_quantity': invoice_line.quantity, + 'unit_sale_price': invoice_line.price_unit, + 'location_dest_id': location_dest_id, + 'state': 'draft', + }) + return {'value': {'claim_line_ids': claim_lines}} diff --git a/crm_claim_rma/stock.py b/crm_claim_rma/stock.py index 500a73be..ae217a6a 100644 --- a/crm_claim_rma/stock.py +++ b/crm_claim_rma/stock.py @@ -19,7 +19,7 @@ # along with this program. If not, see . # ############################################################################## -from openerp.osv import fields, orm, osv +from openerp.osv import fields, orm class stock_picking(orm.Model): @@ -30,24 +30,24 @@ class stock_picking(orm.Model): 'claim_id': fields.many2one('crm.claim', 'Claim'), } - def create(self, cr, user, vals, context=None): - if ('name' not in vals) or (vals.get('name')=='/'): - if vals['type'] != 'internal': - seq_obj_name = 'stock.picking.' + vals['type'] + def create(self, cr, uid, vals, context=None): + if ('name' not in vals) or (vals.get('name') == '/'): + sequence_obj = self.pool.get('ir.sequence') + if vals['type'] == 'internal': + seq_obj_name = self._name else: - seq_obj_name = self._name - vals['name'] = self.pool.get('ir.sequence').get(cr, user, - seq_obj_name, - context=context) - new_id = super(stock_picking, self).create(cr, user, vals, - context=context) + seq_obj_name = 'stock.picking.' + vals['type'] + vals['name'] = sequence_obj.get(cr, uid, seq_obj_name, + context=context) + new_id = super(stock_picking, self).create(cr, uid, vals, + context=context) return new_id class stock_picking_out(orm.Model): _inherit = "stock.picking.out" - + _columns = { 'claim_id': fields.many2one('crm.claim', 'Claim'), } @@ -56,26 +56,27 @@ class stock_picking_out(orm.Model): class stock_picking_out(orm.Model): _inherit = "stock.picking.in" - + _columns = { 'claim_id': fields.many2one('crm.claim', 'Claim'), } -#This part concern the case of a wrong picking out. We need to create a new -#stock_move in a picking already open. -#In order to don't have to confirm the stock_move we override the create and -#confirm it at the creation only for this case +# This part concern the case of a wrong picking out. We need to create a new +# stock_move in a picking already open. +# In order to don't have to confirm the stock_move we override the create and +# confirm it at the creation only for this case class stock_move(orm.Model): - + _inherit = "stock.move" def create(self, cr, uid, vals, context=None): move_id = super(stock_move, self).create(cr, uid, vals, context=context) if vals.get('picking_id'): - picking = self.pool.get('stock.picking').browse(cr, uid, - vals['picking_id'], context=context) + picking_obj = self.pool.get('stock.picking') + picking = picking_obj.browse(cr, uid, vals['picking_id'], + context=context) if picking.claim_id and picking.type == u'in': - move = self.write(cr, uid, move_id, {'state': 'confirmed'}, - context=context) + self.write(cr, uid, move_id, {'state': 'confirmed'}, + context=context) return move_id diff --git a/crm_claim_rma/wizard/account_invoice_refund.py b/crm_claim_rma/wizard/account_invoice_refund.py index f57b608b..386c6751 100644 --- a/crm_claim_rma/wizard/account_invoice_refund.py +++ b/crm_claim_rma/wizard/account_invoice_refund.py @@ -19,26 +19,27 @@ # along with this program. If not, see . # ############################################################################## -from openerp.osv import fields, orm +from openerp.osv import orm class account_invoice_refund(orm.TransientModel): - + _inherit = "account.invoice.refund" def compute_refund(self, cr, uid, ids, mode='refund', context=None): - if context is None: context={} + if context is None: + context={} if context.get('invoice_ids'): context['active_ids'] = context.get('invoice_ids') - return super(account_invoice_refund, self).compute_refund(cr, uid, ids, - mode='refund', context=context) + return super(account_invoice_refund, self).compute_refund( + cr, uid, ids, mode=mode, context=context) def _get_description(self, cr, uid, context=None): - if context is None: context = {} + if context is None: + context = {} description = context.get('description') or '' return description _defaults = { 'description': _get_description, } - diff --git a/crm_claim_rma/wizard/claim_make_picking.py b/crm_claim_rma/wizard/claim_make_picking.py index c4a0a220..1b25752e 100644 --- a/crm_claim_rma/wizard/claim_make_picking.py +++ b/crm_claim_rma/wizard/claim_make_picking.py @@ -2,7 +2,7 @@ ############################################################################## # # Copyright 2013 Camptocamp -# Copyright 2009-2013 Akretion, +# Copyright 2009-2013 Akretion, # Author: Emmanuel Samyn, Raphaël Valyi, Sébastien Beau, Joel Grand-Guillaume # # This program is free software: you can redistribute it and/or modify @@ -20,7 +20,7 @@ # ############################################################################## from openerp.osv import fields, orm, osv -from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT from openerp import netsvc from openerp.tools.translate import _ import time @@ -31,54 +31,62 @@ class claim_make_picking(orm.TransientModel): _name = 'claim_make_picking.wizard' _description = 'Wizard to create pickings from claim lines' _columns = { - 'claim_line_source_location': fields.many2one('stock.location', - 'Source Location', + 'claim_line_source_location': fields.many2one( + 'stock.location', + string='Source Location', help="Location where the returned products are from.", required=True), - 'claim_line_dest_location': fields.many2one('stock.location', - 'Dest. Location', + 'claim_line_dest_location': fields.many2one( + 'stock.location', + string='Dest. Location', help="Location where the system will stock the returned products.", required=True), - 'claim_line_ids': fields.many2many('claim.line', + 'claim_line_ids': fields.many2many( + 'claim.line', 'claim_line_picking', 'claim_picking_id', 'claim_line_id', - 'Claim lines'), + string='Claim lines'), } def _get_claim_lines(self, cr, uid, context): #TODO use custom states to show buttons of this wizard or not instead of raise an error - if context is None: context = {} + if context is None: + context = {} line_obj = self.pool.get('claim.line') if context.get('picking_type') == 'out': move_field = 'move_out_id' else: move_field = 'move_in_id' good_lines = [] - line_ids = line_obj.search(cr, uid, - [('claim_id', '=', context['active_id'])], context=context) + line_ids = line_obj.search( + cr, uid, + [('claim_id', '=', context['active_id'])], + context=context) for line in line_obj.browse(cr, uid, line_ids, context=context): if not line[move_field] or line[move_field].state == 'cancel': good_lines.append(line.id) if not good_lines: - raise osv.except_osv(_('Error !'), - _('A picking has already been created for this claim !')) + raise orm.except_orm( + _('Error'), + _('A picking has already been created for this claim.')) return good_lines # Get default source location def _get_source_loc(self, cr, uid, context): loc_id = False - if context is None: context = {} + if context is None: + context = {} warehouse_obj = self.pool.get('stock.warehouse') warehouse_id = context.get('warehouse_id') if context.get('picking_type') == 'out': - loc_id = warehouse_obj.read(cr, uid, - warehouse_id, + loc_id = warehouse_obj.read( + cr, uid, warehouse_id, ['lot_stock_id'], context=context)['lot_stock_id'][0] elif context.get('partner_id'): - loc_id = self.pool.get('res.partner').read(cr, uid, - context['partner_id'], + loc_id = self.pool.get('res.partner').read( + cr, uid, context['partner_id'], ['property_stock_customer'], context=context)['property_stock_customer'] return loc_id @@ -92,7 +100,7 @@ class claim_make_picking(orm.TransientModel): for line in line_obj.browse(cr, uid, line_ids, context=context): if line.location_dest_id.id not in line_location: line_location.append(line.location_dest_id.id) - if len (line_location) == 1: + if len(line_location) == 1: loc_id = line_location[0] return loc_id @@ -103,33 +111,33 @@ class claim_make_picking(orm.TransientModel): line_obj = self.pool.get('claim.line') line_partner = [] for line in line_obj.browse(cr, uid, line_ids, context=context): - if (line.warranty_return_partner and line.warranty_return_partner.id + if (line.warranty_return_partner and line.warranty_return_partner.id not in line_partner): line_partner.append(line.warranty_return_partner.id) - if len (line_partner) == 1: + if len(line_partner) == 1: partner_id = line_partner[0] return partner_id # Get default destination location def _get_dest_loc(self, cr, uid, context): """Return the location_id to use as destination. - If it's an outoing shippment: take the customer stock property + If it's an outoing shippment: take the customer stock property If it's an incomming shippment take the location_dest_id common to all lines, or if different, return None.""" - if context is None: context = {} - warehouse_obj = self.pool.get('stock.warehouse') - warehouse_id = context.get('warehouse_id') + if context is None: + context = {} loc_id = False if context.get('picking_type') == 'out' and context.get('partner_id'): - loc_id = self.pool.get('res.partner').read(cr, uid, - context.get('partner_id'), + loc_id = self.pool.get('res.partner').read( + cr, uid, context.get('partner_id'), ['property_stock_customer'], context=context)['property_stock_customer'][0] elif context.get('picking_type') == 'in' and context.get('partner_id'): - # Add the case of return to supplier ! + # Add the case of return to supplier ! line_ids = self._get_claim_lines(cr, uid, context=context) - loc_id = self._get_common_dest_location_from_line(cr, uid, - line_ids, context=context) + loc_id = self._get_common_dest_location_from_line(cr, uid, + line_ids, + context=context) return loc_id _defaults = { @@ -138,13 +146,14 @@ class claim_make_picking(orm.TransientModel): 'claim_line_ids': _get_claim_lines, } - def action_cancel(self,cr,uid,ids,conect=None): - return {'type': 'ir.actions.act_window_close',} + def action_cancel(self, cr, uid, ids, context=None): + return {'type': 'ir.actions.act_window_close'} # If "Create" button pressed def action_create_picking(self, cr, uid, ids, context=None): picking_obj = self.pool.get('stock.picking') - if context is None: context = {} + if context is None: + context = {} view_obj = self.pool.get('ir.ui.view') name = 'RMA picking out' if context.get('picking_type') == 'out': @@ -161,87 +170,96 @@ class claim_make_picking(orm.TransientModel): if context.get('picking_type'): note = 'RMA picking ' + str(context.get('picking_type')) name = note - view_id = view_obj.search(cr, uid, [ - ('xml_id', '=', view_xml_id), - ('model', '=', 'stock.picking'), - ('type', '=', 'form'), - ('name', '=', view_name) - ], context=context)[0] + view_id = view_obj.search(cr, uid, + [('xml_id', '=', view_xml_id), + ('model', '=', 'stock.picking'), + ('type', '=', 'form'), + ('name', '=', view_name) + ], + context=context)[0] wizard = self.browse(cr, uid, ids[0], context=context) - claim = self.pool.get('crm.claim').browse(cr, uid, - context['active_id'], context=context) + claim = self.pool.get('crm.claim').browse(cr, uid, + context['active_id'], + context=context) partner_id = claim.partner_id.id line_ids = [x.id for x in wizard.claim_line_ids] # In case of product return, we don't allow one picking for various # product if location are different # or if partner address is different if context.get('product_return'): - common_dest_loc_id = self._get_common_dest_location_from_line(cr, uid, - line_ids, context=context) + common_dest_loc_id = self._get_common_dest_location_from_line( + cr, uid, line_ids, context=context) if not common_dest_loc_id: - raise osv.except_osv(_('Error !'), - _('A product return cannot be created for various destination location, please ' - 'chose line with a same destination location.')) - common_dest_partner_id = self._get_common_partner_from_line(cr, uid, - line_ids, context=context) + raise orm.except_orm( + _('Error !'), + _('A product return cannot be created for various ' + 'destination locations, please choose line with a ' + 'same destination location.')) + common_dest_partner_id = self._get_common_partner_from_line( + cr, uid, line_ids, context=context) if not common_dest_partner_id: - raise osv.except_osv(_('Error !'), - _('A product return cannot be created for various destination address, please ' - 'chose line with a same address.')) - else: - partner_id = common_dest_partner_id + raise orm.except_orm( + _('Error !'), + _('A product return cannot be created for various ' + 'destination addresses, please choose line with a ' + 'same address.')) + partner_id = common_dest_partner_id # create picking - picking_id = picking_obj.create(cr, uid, { - 'origin': claim.number, - 'type': p_type, - 'move_type': 'one', # direct - 'state': 'draft', - 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), - 'partner_id': partner_id, - 'invoice_state': "none", - 'company_id': claim.company_id.id, - 'location_id': wizard.claim_line_source_location.id, - 'location_dest_id': wizard.claim_line_dest_location.id, - 'note' : note, - 'claim_id': claim.id, - }) + picking_id = picking_obj.create( + cr, uid, + {'origin': claim.number, + 'type': p_type, + 'move_type': 'one', # direct + 'state': 'draft', + 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'partner_id': partner_id, + 'invoice_state': "none", + 'company_id': claim.company_id.id, + 'location_id': wizard.claim_line_source_location.id, + 'location_dest_id': wizard.claim_line_dest_location.id, + 'note': note, + 'claim_id': claim.id, + }, + context=context) # Create picking lines for wizard_claim_line in wizard.claim_line_ids: - move_id = self.pool.get('stock.move').create(cr, uid, { - 'name' : wizard_claim_line.product_id.name_template, # Motif : crm id ? stock_picking_id ? - 'priority': '0', - #'create_date': - 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), - 'date_expected': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), - 'product_id': wizard_claim_line.product_id.id, - 'product_qty': wizard_claim_line.product_returned_quantity, - 'product_uom': wizard_claim_line.product_id.uom_id.id, - 'partner_id': partner_id, - 'prodlot_id': wizard_claim_line.prodlot_id.id, - # 'tracking_id': - 'picking_id': picking_id, - 'state': 'draft', - 'price_unit': wizard_claim_line.unit_sale_price, - # 'price_currency_id': claim_id.company_id.currency_id.id, # from invoice ??? - 'company_id': claim.company_id.id, - 'location_id': wizard.claim_line_source_location.id, - 'location_dest_id': wizard.claim_line_dest_location.id, - 'note': note, - }) - self.pool.get('claim.line').write(cr, uid, - wizard_claim_line.id, {write_field: move_id}, context=context) + move_obj = self.pool.get('stock.move') + move_id = move_obj.create( + cr, uid, + {'name': wizard_claim_line.product_id.name_template, + 'priority': '0', + 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'date_expected': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'product_id': wizard_claim_line.product_id.id, + 'product_qty': wizard_claim_line.product_returned_quantity, + 'product_uom': wizard_claim_line.product_id.uom_id.id, + 'partner_id': partner_id, + 'prodlot_id': wizard_claim_line.prodlot_id.id, + 'picking_id': picking_id, + 'state': 'draft', + 'price_unit': wizard_claim_line.unit_sale_price, + 'company_id': claim.company_id.id, + 'location_id': wizard.claim_line_source_location.id, + 'location_dest_id': wizard.claim_line_dest_location.id, + 'note': note, + }, + context=context) + self.pool.get('claim.line').write( + cr, uid, wizard_claim_line.id, + {write_field: move_id}, context=context) wf_service = netsvc.LocalService("workflow") if picking_id: - wf_service.trg_validate(uid, 'stock.picking', - picking_id,'button_confirm', cr) + wf_service.trg_validate(uid, 'stock.picking', + picking_id, 'button_confirm', cr) picking_obj.action_assign(cr, uid, [picking_id]) - domain = "[('type','=','%s'),('partner_id','=',%s)]"%(p_type, partner_id) + domain = ("[('type', '=', '%s'), ('partner_id', '=', %s)]" % + (p_type, partner_id)) return { 'name': '%s' % name, 'view_type': 'form', 'view_mode': 'form', 'view_id': view_id, - 'domain' : domain, + 'domain': domain, 'res_model': 'stock.picking', 'res_id': picking_id, 'type': 'ir.actions.act_window',