From 611383f4c35d5aedea7bbe2f9161e62cba15df56 Mon Sep 17 00:00:00 2001 From: manu Date: Wed, 12 Oct 2011 15:01:59 +0200 Subject: [PATCH] [INIT] crm_claim_rma & product_warranty --- crm_claim_rma/__init__.py | 26 + crm_claim_rma/__openerp__.py | 58 ++ crm_claim_rma/crm_claim_rma.py | 260 +++++ crm_claim_rma/crm_claim_rma_data.xml | 83 ++ crm_claim_rma/crm_claim_rma_view.xml | 331 +++++++ crm_claim_rma/i18n/fr.po | 889 ++++++++++++++++++ crm_claim_rma/invoice_line_warranty.py | 47 + crm_claim_rma/rma_substate_data.xml | 52 + crm_claim_rma/wizard/__init__.py | 30 + .../wizard/exchange_from_returned_lines.py | 105 +++ ...change_from_returned_lines_wizard_view.xml | 90 ++ crm_claim_rma/wizard/get_empty_serial.py | 61 ++ .../wizard/get_empty_serial_view.xml | 89 ++ .../wizard/picking_from_exchange_lines.py | 116 +++ ...icking_from_exchange_lines_wizard_view.xml | 57 ++ .../wizard/picking_from_returned_lines.py | 213 +++++ ...icking_from_returned_lines_wizard_view.xml | 89 ++ .../wizard/refund_from_returned_lines.py | 121 +++ ...refund_from_returned_lines_wizard_view.xml | 57 ++ .../wizard/returned_lines_from_invoice.py | 180 ++++ ...eturned_lines_from_invoice_wizard_view.xml | 89 ++ .../wizard/returned_lines_from_serial.py | 244 +++++ ...returned_lines_from_serial_wizard_view.xml | 74 ++ product_warranty/__init__.py | 23 + product_warranty/__openerp__.py | 45 + product_warranty/product_warranty.py | 46 + product_warranty/product_warranty_view.xml | 53 ++ 27 files changed, 3528 insertions(+) create mode 100644 crm_claim_rma/__init__.py create mode 100644 crm_claim_rma/__openerp__.py create mode 100644 crm_claim_rma/crm_claim_rma.py create mode 100644 crm_claim_rma/crm_claim_rma_data.xml create mode 100644 crm_claim_rma/crm_claim_rma_view.xml create mode 100644 crm_claim_rma/i18n/fr.po create mode 100644 crm_claim_rma/invoice_line_warranty.py create mode 100644 crm_claim_rma/rma_substate_data.xml create mode 100644 crm_claim_rma/wizard/__init__.py create mode 100644 crm_claim_rma/wizard/exchange_from_returned_lines.py create mode 100644 crm_claim_rma/wizard/exchange_from_returned_lines_wizard_view.xml create mode 100644 crm_claim_rma/wizard/get_empty_serial.py create mode 100644 crm_claim_rma/wizard/get_empty_serial_view.xml create mode 100644 crm_claim_rma/wizard/picking_from_exchange_lines.py create mode 100644 crm_claim_rma/wizard/picking_from_exchange_lines_wizard_view.xml create mode 100644 crm_claim_rma/wizard/picking_from_returned_lines.py create mode 100644 crm_claim_rma/wizard/picking_from_returned_lines_wizard_view.xml create mode 100644 crm_claim_rma/wizard/refund_from_returned_lines.py create mode 100644 crm_claim_rma/wizard/refund_from_returned_lines_wizard_view.xml create mode 100644 crm_claim_rma/wizard/returned_lines_from_invoice.py create mode 100644 crm_claim_rma/wizard/returned_lines_from_invoice_wizard_view.xml create mode 100644 crm_claim_rma/wizard/returned_lines_from_serial.py create mode 100644 crm_claim_rma/wizard/returned_lines_from_serial_wizard_view.xml create mode 100644 product_warranty/__init__.py create mode 100644 product_warranty/__openerp__.py create mode 100644 product_warranty/product_warranty.py create mode 100644 product_warranty/product_warranty_view.xml diff --git a/crm_claim_rma/__init__.py b/crm_claim_rma/__init__.py new file mode 100644 index 00000000..727f7842 --- /dev/null +++ b/crm_claim_rma/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +######################################################################### +# # +# # +######################################################################### +# # +# Copyright (C) 2009-2011 Akretion, Raphaël Valyi, Sébastien Beau, # +# Emmanuel Samyn # +# # +#This program is free software: you can redistribute it and/or modify # +#it under the terms of the GNU General Public License as published by # +#the Free Software Foundation, either version 3 of the License, or # +#(at your option) any later version. # +# # +#This program is distributed in the hope that it will be useful, # +#but WITHOUT ANY WARRANTY; without even the implied warranty of # +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +#GNU General Public License for more details. # +# # +#You should have received a copy of the GNU General Public License # +#along with this program. If not, see . # +######################################################################### + +import wizard +import crm_claim_rma +import invoice_line_warranty diff --git a/crm_claim_rma/__openerp__.py b/crm_claim_rma/__openerp__.py new file mode 100644 index 00000000..7f9a20c2 --- /dev/null +++ b/crm_claim_rma/__openerp__.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +######################################################################### +# # +# # +######################################################################### +# # +# Copyright (C) 2009-2011 Akretion, Raphaël Valyi, Sébastien Beau, # +# Emmanuel Samyn # +# # +#This program is free software: you can redistribute it and/or modify # +#it under the terms of the GNU General Public License as published by # +#the Free Software Foundation, either version 3 of the License, or # +#(at your option) any later version. # +# # +#This program is distributed in the hope that it will be useful, # +#but WITHOUT ANY WARRANTY; without even the implied warranty of # +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +#GNU General Public License for more details. # +# # +#You should have received a copy of the GNU General Public License # +#along with this program. If not, see . # +######################################################################### + + +{ + 'name': 'CRM Product Return', + 'version': '1.0', + 'category': 'Generic Modules/CRM & SRM', + 'description': """ +Add product return functionalities, product exchange and aftersale outsourcing to CRM claim + """, + 'author': 'Akretion - Emmanuel Samyn', + 'website': 'http://www.akretion.com', + 'depends': ['sale','stock','crm_claim'], + 'init_xml': ['rma_substate_data.xml',], + 'update_xml': [ + 'wizard/returned_lines_from_serial_wizard_view.xml', + 'wizard/returned_lines_from_invoice_wizard_view.xml', + 'wizard/picking_from_returned_lines_wizard_view.xml', + 'wizard/refund_from_returned_lines_wizard_view.xml', + 'wizard/exchange_from_returned_lines_wizard_view.xml', + 'wizard/picking_from_exchange_lines_wizard_view.xml', + 'wizard/get_empty_serial_view.xml', + 'crm_claim_rma_view.xml', + + +# 'security/ir.model.access.csv', + # 'report/crm_claim_report_view.xml', + ], + 'demo_xml': [ + # 'crm_claim_demo.xml', + ], +# 'test': ['test/test_crm_claim.yml'], + 'installable': True, + 'active': False, +} + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/crm_claim_rma/crm_claim_rma.py b/crm_claim_rma/crm_claim_rma.py new file mode 100644 index 00000000..e45b6a58 --- /dev/null +++ b/crm_claim_rma/crm_claim_rma.py @@ -0,0 +1,260 @@ +# -*- coding: utf-8 -*- +######################################################################### +# # +# # +######################################################################### +# # +# Copyright (C) 2009-2011 Akretion, Raphaël Valyi, Sébastien Beau, # +# Emmanuel Samyn # +# # +#This program is free software: you can redistribute it and/or modify # +#it under the terms of the GNU General Public License as published by # +#the Free Software Foundation, either version 3 of the License, or # +#(at your option) any later version. # +# # +#This program is distributed in the hope that it will be useful, # +#but WITHOUT ANY WARRANTY; without even the implied warranty of # +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +#GNU General Public License for more details. # +# # +#You should have received a copy of the GNU General Public License # +#along with this program. If not, see . # +######################################################################### + +from osv import fields, osv +from crm import crm +from datetime import datetime +from dateutil.relativedelta import relativedelta +import time + +#===== TO REFACTOR IN A GENERIC MODULE +class substate_substate(osv.osv): + """ + 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 + } +substate_substate() + +#===== +class return_line(osv.osv): + """ + Class to handle a product return line (corresponding to one invoice line) + """ + _name = "return.line" + _description = "List of product to return" + + # Method to calculate total amount of the line : qty*UP + def _line_total_amount(self, cr, uid, ids, field_name, arg,context): + res = {} + for line in self.browse(cr,uid,ids): + res[line.id] = line.unit_sale_price*line.product_returned_quantity + return res + + def _get_claim_seq(self, cr, uid, ids, field_name, arg,context): + res = {} + for line in self.browse(cr,uid,ids): + res[line.id] = line.claim_id.sequence + return res + + _columns = { + 'name': fields.function(_get_claim_seq, method=True, string='Claim n°', type='char', size=64,store=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"), + 'invoice_id': fields.many2one('account.invoice', 'Invoice',help="The invoice related to the returned product"), + 'product_id': fields.many2one('product.product', 'Product',help="Returned product"), + '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 discount, can be for 0 if product for free,..."), + 'return_value' : fields.function(_line_total_amount, method=True, string='Total return', type='float', help="Quantity returned * Unit sold price",), + 'prodlot_id': fields.many2one('stock.production.lot', 'Serial/Lot n°',help="The serial/lot of the returned product"), + 'applicable_guarantee': fields.selection([('us','Us'),('supplier','Supplier'),('brand','Brand manufacturer')], 'Warranty type'),# METTRE CHAMP FONCTION; type supplier might generate an auto draft forward to the supplier + 'guarantee_limit': fields.date('Warranty limit', help="The warranty limit is computed as: invoice date + warranty defined on selected product.", readonly=True), + 'warning': fields.char('Warranty', size=64, readonly=True,help="If warranty has expired"), #select=1, + "warranty_return_partner" : fields.many2one('res.partner.address', 'Warranty return',help="Where the customer has to send back the product(s)"), + 'claim_id': fields.many2one('crm.claim', 'Related claim',help="To link to the case.claim object"), + 'selected' : fields.boolean('s', help="Check to select"), + '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',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', help="To set the last state / substate change"), + } + + _defaults = { + 'state': lambda *a: 'draft', + 'name': lambda *a: 'none', + } + + # Method to calculate warranty limit + def set_warranty_limit(self, cr, uid, ids,context,return_line): + warning = "Valid" + limit = (datetime.strptime(return_line.invoice_id.date_invoice, '%Y-%m-%d') + relativedelta(months=int(return_line.product_id.warranty))).strftime('%Y-%m-%d') + if limit < return_line.claim_id.date: + warning = 'Expired' + self.write(cr,uid,ids,{ + 'guarantee_limit' : limit, + 'warning' : warning, + }) + return True + + # Method to return the partner delivery address or if none, the default address + def _get_partner_address(self, cr, uid, ids, context,partner): + print "IN GET COMP" + print "partner: ",partner + delivery_address_id = self.pool.get('res.partner.address').search(cr, uid, [('partner_id','=',partner.id),('type','like','delivery')]) + if delivery_address_id: + print delivery_address_id + return delivery_address_id + else: + print "default address : ",self.pool.get('res.partner.address').search(cr, uid, [('partner_id','=',partner.id),('type','like','default')]) + return self.pool.get('res.partner.address').search(cr, uid, [('partner_id','=',partner.id),('type','like','default')]) + + # Method to calculate warranty return address + def set_warranty_return_address(self, cr, uid, ids,context,return_line): + print "IN WAR ADDRESS : ",return_line + return_address = None + if return_line.prodlot_id : + # multi supplier method + print "true" + else : + # first supplier method + if return_line.product_id.seller_ids[0]: + if return_line.product_id.seller_ids[0].warranty_return_partner: + return_partner = return_line.product_id.seller_ids[0].warranty_return_partner + print "adresse retour : ",return_partner + if return_partner == 'company': + return_address = self._get_partner_address(cr, uid, ids, context,return_line.claim_id.company_id.partner_id)[0] + elif return_partner == 'supplier': + return_address = self._get_partner_address(cr, uid, ids, context,return_line.product_id.seller_ids[0].name)[0] + else : + print "brand" + else : + print "popup erreur suppl" + + self.write(cr,uid,ids,{'warranty_return_partner' : return_address}) + return True + + # Method to calculate warranty limit and validity + def set_warranty(self, cr, uid, ids,context): + for return_line in self.browse(cr,uid,ids): + if return_line.product_id and return_line.invoice_id: + self.set_warranty_limit(cr, uid, ids,context,return_line) + self.set_warranty_return_address(cr, uid, ids,context,return_line) + else: + self.write(cr,uid,ids,{'warning' : "PLEASE SET PRODUCT & INVOICE",}) + return True + +return_line() + +#===== +class product_exchange(osv.osv): + """ + Class to manage product exchanges history + """ + _name = "product.exchange" + _description = "exchange history line" + + # Method to calculate total amount of the line : qty*UP + def total_amount_returned(self, cr, uid, ids, field_name, arg,context): + res = {} + for line in self.browse(cr,uid,ids): + res[line.id] = line.returned_unit_sale_price*line.returned_product_qty + return res + + # Method to calculate total amount of the line : qty*UP + def total_amount_replacement(self, cr, uid, ids, field_name, arg,context): + res = {} + for line in self.browse(cr,uid,ids): + res[line.id] = line.replacement_unit_sale_price*line.replacement_product_qty + return res + + # Method to get the replacement product unit price + def get_replacement_price(self, cr, uid, ids, field_name, arg,context): + res = {} + for line in self.browse(cr,uid,ids): + res[line.id] = line.replacement_product.list_price + return res + + _columns = { + 'name': fields.char('Comment', size=128, required=True), + 'exchange_send_date' : fields.date('Exchange date'), + 'returned_product' : fields.many2one('product.product', 'Returned product', required=True), # ADD FILTER ON RETURNED PROD + 'returned_product_serial' : fields.many2one('stock.production.lot', 'Returned serial/Lot n°'), + 'returned_product_qty' : fields.float('Returned quantity', digits=(12,2), help="Quantity of product returned"), + 'replacement_product' : fields.many2one('product.product', 'Replacement product', required=True), + 'replacement_product_serial' : fields.many2one('stock.production.lot', 'Replacement serial/Lot n°'), + 'replacement_product_qty' : fields.float('Replacement quantity', digits=(12,2), help="Quantity of product replaced"), + 'claim_return_id' : fields.many2one('crm.claim', 'Related claim'), # To link to the case.claim object + 'selected' : fields.boolean('s', help="Check to select"), + 'state' : fields.selection([('draft','Draft'), + ('confirmed','Confirmed'), + ('to_send','To send'), + ('sent','Sent')], 'State'), + 'returned_unit_sale_price' : fields.float('Unit sale price', digits=(12,2)), + 'returned_value' : fields.function(total_amount_returned, method=True, string='Total return', type='float', help="Quantity exchanged * Unit sold price",), + 'replacement_unit_sale_price' : fields.function(get_replacement_price, method=True, string='Unit sale price', type='float',), + 'replacement_value' : fields.function(total_amount_replacement, method=True, string='Total return', type='float', help="Quantity replaced * Unit sold price",), + } + _defaults = { + 'state': lambda *a: 'draft', + } + +product_exchange() + +#========== +class crm_claim_product_return(osv.osv): + """ + Class to add RMA management on a standard crm.claim object + """ + _name = "crm.claim" + _description = "Add product return functionalities, product exchange and aftersale outsourcing to CRM claim" + _inherit = 'crm.claim' + _columns = { + 'sequence': fields.char('Sequence', size=128, required=True,readonly=True, help="Company internal claim unique number"), + 'return_line_ids' : fields.one2many('return.line', 'claim_id', 'Return lines'), + 'product_exchange_ids': fields.one2many('product.exchange', 'claim_return_id', 'Product exchanges'), + # Aftersale outsourcing +# 'in_supplier_picking_id': fields.many2one('stock.picking', 'Return To Supplier Picking', required=False, select=True), +# 'out_supplier_picking_id': fields.many2one('stock.picking', 'Return From Supplier Picking', required=False, select=True), + + # Financial management + 'planned_revenue': fields.float('Expected revenue'), + 'planned_cost': fields.float('Expected cost'), + 'real_revenue': fields.float('Real revenue'), # A VOIR SI COMPTA ANA ou lien vers compte ana ? + 'real_cost': fields.float('Real cost'), # A VOIR SI COMPTA ANA ou lien vers compte ana ? + } + _defaults = { + 'sequence': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'crm.claim.rma'),} + + #===== Method to select all returned lines ===== + def select_all(self,cr, uid, ids,context): + return_obj = self.pool.get('return.line') + for line in self.browse(cr,uid,ids)[0].return_line_ids: + return_obj.write(cr,uid,line.id,{'selected':True}) + return True + + #===== Method to unselect all returned lines ===== + def unselect_all(self,cr, uid, ids,context): + return_obj = self.pool.get('return.line') + for line in self.browse(cr,uid,ids)[0].return_line_ids: + return_obj.write(cr,uid,line.id,{'selected':False}) + return True + +crm_claim_product_return() + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/crm_claim_rma/crm_claim_rma_data.xml b/crm_claim_rma/crm_claim_rma_data.xml new file mode 100644 index 00000000..bf3a4447 --- /dev/null +++ b/crm_claim_rma/crm_claim_rma_data.xml @@ -0,0 +1,83 @@ + + + + + + CRM Claim + crm.claim.rma + + + + CRM Claim + crm.claim.rma + + RMA-%(year)s/ + + + + + + Factual Claims + + + + + + Value Claims + + + + + + Policy Claims + + + + + + + + Corrective + + + + + Preventive + + + + + + + Accepted as Claim + 1 + claim + + + Actions Defined + 2 + claim + + + Actions Done + 10 + claim + + + Won't fix + 0 + claim + + + + + Sales Department + Sales + + + + diff --git a/crm_claim_rma/crm_claim_rma_view.xml b/crm_claim_rma/crm_claim_rma_view.xml new file mode 100644 index 00000000..63206a24 --- /dev/null +++ b/crm_claim_rma/crm_claim_rma_view.xml @@ -0,0 +1,331 @@ + + + + + + + CRM - Claims Search + return.line + search + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CRM - Claims Tree + return.line + tree + + + + + + + + + + + + + + + + + + CRM - Claim product return line Form + return.line + +
+ + + + + + + + + + + + + + + +