diff --git a/.travis.yml b/.travis.yml index d55c3355..6ef26cb1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,8 @@ install: - git clone https://github.com/OCA/maintainer-quality-tools.git $HOME/maintainer-quality-tools - export PATH=$HOME/maintainer-quality-tools/travis:$PATH - travis_install_nightly + # add OCA/crm dependencies + - git clone --depth=1 https://github.com/OCA/crm -b ${VERSION} $HOME/crm script: - travis_run_tests diff --git a/__unported__/crm_claim_rma/account_invoice.py b/__unported__/crm_claim_rma/account_invoice.py deleted file mode 100644 index aab0b57c..00000000 --- a/__unported__/crm_claim_rma/account_invoice.py +++ /dev/null @@ -1,109 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Copyright 2013 Camptocamp -# Copyright 2009-2013 Akretion, -# Author: Emmanuel Samyn, Raphaël Valyi, Sébastien Beau, -# Benoît Guillot, Joel Grand-Guillaume -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero 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 Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -from openerp.osv import fields, orm -from tools.translate import _ - - -class account_invoice(orm.Model): - - _inherit = "account.invoice" - - _columns = { - 'claim_id': fields.many2one('crm.claim', 'Claim'), - } - - 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 = [] - 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, l) for l 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['claim_id'] - return result - - -class account_invoice_line(orm.Model): - - _inherit = "account.invoice.line" - - def create(self, cr, uid, vals, context=None): - claim_line_id = False - 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) - 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) - return line_id diff --git a/__unported__/crm_claim_rma/crm_claim_rma.py b/__unported__/crm_claim_rma/crm_claim_rma.py deleted file mode 100644 index 5f9fe4f4..00000000 --- a/__unported__/crm_claim_rma/crm_claim_rma.py +++ /dev/null @@ -1,680 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Copyright 2013 Camptocamp -# Copyright 2009-2013 Akretion, -# Author: Emmanuel Samyn, Raphaël Valyi, Sébastien Beau, -# Benoît Guillot, Joel Grand-Guillaume -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero 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 Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -import calendar -import math -from openerp.osv import fields, orm, osv -from datetime import datetime -from dateutil.relativedelta import relativedelta -from openerp.tools import (DEFAULT_SERVER_DATE_FORMAT, - DEFAULT_SERVER_DATETIME_FORMAT) -from openerp.tools.translate import _ -from openerp import SUPERUSER_ID - - -class InvoiceNoDate(Exception): - """ Raised when a warranty cannot be computed for a claim line - because the invoice has no date. """ - - -class ProductNoSupplier(Exception): - """ Raised when a warranty cannot be computed for a claim line - because the product has no supplier. """ - - -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', required=True), - 'substate_descr': fields.text( - 'Description', - help="To give more information about the sub state"), - } - - -class claim_line(orm.Model): - """ - Class to handle a product return line (corresponding to one invoice line) - """ - _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, 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): - if default is None: - default = {} - std_default = { - 'move_in_id': False, - 'move_out_id': False, - 'refund_line_id': False, - } - 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', 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', - string='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 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, string='Total return', type='float', - help="Quantity returned * Unit sold price",), - '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')], - 'Warranty type'), - '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', - readonly=True, - help="If warranty has expired"), - 'warranty_type': fields.selection( - get_warranty_return_partner, - 'Warranty type', - readonly=True, - 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', 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')], - 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( - string='Last change', - help="To set the last state / substate change"), - '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', - string='Refund Line', - help='The refund line related to the returned product'), - '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', - string='Move Line from picking out', - help='The move line related to the returned product'), - 'location_dest_id': fields.many2one( - 'stock.location', - string='Return Stock Location', - help='The return stock location of the returned product'), - } - - _defaults = { - 'state': 'draft', - 'name': 'none', - } - - @staticmethod - def warranty_limit(start, warranty_duration): - """ Take a duration in float, return the duration in relativedelta - - ``relative_delta(months=...)`` only accepts integers. - We have to extract the decimal part, and then, extend the delta with - days. - - """ - decimal_part, months = math.modf(warranty_duration) - months = int(months) - # If we have a decimal part, we add the number them as days to - # the limit. We need to get the month to know the number of - # days. - delta = relativedelta(months=months) - monthday = start + delta - __, days_month = calendar.monthrange(monthday.year, monthday.month) - # ignore the rest of the days (hours) since we expect a date - days = int(days_month * decimal_part) - return start + relativedelta(months=months, days=days) - - def _warranty_limit_values(self, cr, uid, ids, invoice, - claim_type, product, claim_date, - context=None): - if not (invoice and claim_type and product and claim_date): - return {'guarantee_limit': False, 'warning': False} - date_invoice = invoice.date_invoice - if not date_invoice: - raise InvoiceNoDate - warning = _(self.WARRANT_COMMENT['not_define']) - date_invoice = datetime.strptime(date_invoice, - DEFAULT_SERVER_DATE_FORMAT) - if claim_type == 'supplier': - suppliers = product.seller_ids - if not suppliers: - raise ProductNoSupplier - supplier = suppliers[0] - warranty_duration = supplier.warranty_duration - else: - warranty_duration = product.warranty - limit = self.warranty_limit(date_invoice, warranty_duration) - if warranty_duration > 0: - claim_date = datetime.strptime(claim_date, - DEFAULT_SERVER_DATETIME_FORMAT) - if limit < claim_date: - warning = _(self.WARRANT_COMMENT['expired']) - else: - warning = _(self.WARRANT_COMMENT['valid']) - return {'guarantee_limit': limit.strftime(DEFAULT_SERVER_DATE_FORMAT), - 'warning': warning} - - def set_warranty_limit(self, cr, uid, ids, claim_line, context=None): - claim = claim_line.claim_id - invoice = claim.invoice_id - claim_type = claim.claim_type - claim_date = claim.date - product = claim_line.product_id - try: - values = self._warranty_limit_values(cr, uid, ids, invoice, - claim_type, product, - claim_date, - context=context) - except InvoiceNoDate: - raise orm.except_orm( - _('Error'), - _('Cannot find any date for invoice. ' - 'Must be a validated invoice.')) - except ProductNoSupplier: - raise orm.except_orm( - _('Error'), - _('The product has no supplier configured.')) - self.write(cr, uid, ids, values, context=context) - return True - - def auto_set_warranty(self, cr, uid, ids, context): - """ Set warranty automatically - if the user has not himself pressed on 'Calculate warranty state' - button, it sets warranty for him""" - for line in self.browse(cr, uid, ids, context=context): - if not line.warning: - self.set_warranty(cr, uid, [line.id], context=context) - return True - - 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.""" - prod = False - if product_id: - prod_obj = self.pool.get('product.product') - prod = prod_obj.browse(cr, uid, product_id, context=context) - wh_obj = self.pool.get('stock.warehouse') - wh = wh_obj.browse(cr, uid, warehouse_id, context=context) - location_dest_id = wh.lot_stock_id.id - if prod: - seller = prod.seller_info_id - if seller: - return_type = seller.warranty_return_partner - if return_type != 'company': - location_dest_id = seller.name.property_stock_supplier.id - return location_dest_id - - def onchange_product_id(self, cr, uid, ids, product_id, invoice_line_id, - claim_id, company_id, warehouse_id, - claim_type, claim_date, context=None): - if not claim_id and not (company_id and warehouse_id and - claim_type and claim_date): - # if we have a claim_id, we get the info from there, - # otherwise we get it from the args (on creation typically) - return {} - if not (product_id and invoice_line_id): - return {} - product_obj = self.pool['product.product'] - claim_obj = self.pool['crm.claim'] - invoice_line_obj = self.pool['account.invoice.line'] - claim_line_obj = self.pool.get('claim.line') - product = product_obj.browse(cr, uid, product_id, context=context) - invoice_line = invoice_line_obj.browse(cr, uid, invoice_line_id, - context=context) - invoice = invoice_line.invoice_id - - if claim_id: - claim = claim_obj.browse(cr, uid, claim_id, context=context) - company = claim.company_id - warehouse = claim.warehouse_id - claim_type = claim.claim_type - claim_date = claim.date - else: - warehouse_obj = self.pool['stock.warehouse'] - company_obj = self.pool['res.company'] - company = company_obj.browse(cr, uid, company_id, context=context) - warehouse = warehouse_obj.browse(cr, uid, warehouse_id, - context=context) - - values = {} - try: - warranty = claim_line_obj._warranty_limit_values( - cr, uid, [], invoice, - claim_type, product, - claim_date, context=context) - except (InvoiceNoDate, ProductNoSupplier): - # we don't mind at this point if the warranty can't be - # computed and we don't want to block the user - values.update({'guarantee_limit': False, 'warning': False}) - else: - values.update(warranty) - - warranty_address = claim_line_obj._warranty_return_address_values( - cr, uid, [], product, company, warehouse, context=context) - values.update(warranty_address) - return {'value': values} - - def _warranty_return_address_values(self, cr, uid, ids, product, company, - warehouse, 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 - - """ - if not (product and company and warehouse): - return {'warranty_return_partner': False, - 'warranty_type': False, - 'location_dest_id': False} - return_address = None - seller = product.seller_info_id - if seller: - return_address_id = seller.warranty_return_address.id - return_type = seller.warranty_return_partner - else: - # when no supplier is configured, returns to the company - return_address = (company.crm_return_address_id or - company.partner_id) - return_address_id = return_address.id - return_type = 'company' - location_dest_id = self.get_destination_location( - cr, uid, product.id, warehouse.id, context=context) - return {'warranty_return_partner': return_address_id, - 'warranty_type': return_type, - 'location_dest_id': location_dest_id} - - def set_warranty_return_address(self, cr, uid, ids, claim_line, - context=None): - claim = claim_line.claim_id - product = claim_line.product_id - company = claim.company_id - warehouse = claim.warehouse_id - values = self._warranty_return_address_values( - cr, uid, ids, product, company, warehouse, context=context) - self.write(cr, uid, ids, values, context=context) - return True - - 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 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 -class crm_claim(orm.Model): - _inherit = 'crm.claim' - - def init(self, cr): - cr.execute(""" - UPDATE "crm_claim" SET "number"=id::varchar - WHERE ("number" is NULL) - OR ("number" = '/'); - """) - - def _get_sequence_number(self, cr, uid, context=None): - 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): - 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 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): - res = [] - if isinstance(ids, (int, long)): - ids = [ids] - for claim in self.browse(cr, uid, ids, context=context): - number = claim.number and str(claim.number) or '' - res.append((claim.id, '[' + number + '] ' + claim.name)) - return res - - def create(self, cr, uid, vals, context=None): - 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=context) - return new_id - - def copy_data(self, cr, uid, id, default=None, context=None): - if default is None: - default = {} - std_default = { - 'invoice_ids': False, - 'picking_ids': False, - 'number': self._get_sequence_number(cr, uid, context=context), - } - std_default.update(default) - return super(crm_claim, self).copy_data( - cr, uid, id, default=std_default, context=context) - - _columns = { - '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')], - string='Claim type', - required=True, - 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', string='Invoice', - help='Related original Cusotmer invoice'), - 'delivery_address_id': fields.many2one( - 'res.partner', string='Partner delivery address', - help="This address will be used to deliver repaired or replacement" - "products."), - 'warehouse_id': fields.many2one( - 'stock.warehouse', string='Warehouse', - required=True), - } - - _defaults = { - 'number': '/', - 'claim_type': 'customer', - 'warehouse_id': _get_default_warehouse, - } - - _sql_constraints = [ - ('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) - if add: - if (not res['value']['email_from'] - or not res['value']['partner_phone']): - 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 - if other_add.phone and not res['value']['partner_phone']: - res['value']['partner_phone'] = other_add.phone - return res - - def onchange_invoice_id(self, cr, uid, ids, invoice_id, warehouse_id, - claim_type, claim_date, company_id, lines, - create_lines=False, context=None): - invoice_line_obj = self.pool.get('account.invoice.line') - invoice_obj = self.pool.get('account.invoice') - product_obj = self.pool['product.product'] - claim_line_obj = self.pool.get('claim.line') - company_obj = self.pool['res.company'] - warehouse_obj = self.pool['stock.warehouse'] - invoice_line_ids = invoice_line_obj.search( - cr, uid, - [('invoice_id', '=', invoice_id)], - context=context) - claim_lines = [] - value = {} - if not warehouse_id: - warehouse_id = self._get_default_warehouse(cr, uid, - context=context) - invoice_lines = invoice_line_obj.browse(cr, uid, invoice_line_ids, - context=context) - - def warranty_values(invoice, product): - values = {} - try: - warranty = claim_line_obj._warranty_limit_values( - cr, uid, [], invoice, - claim_type, product, - claim_date, context=context) - except (InvoiceNoDate, ProductNoSupplier): - # we don't mind at this point if the warranty can't be - # computed and we don't want to block the user - values.update({'guarantee_limit': False, 'warning': False}) - else: - values.update(warranty) - company = company_obj.browse(cr, uid, company_id, context=context) - warehouse = warehouse_obj.browse(cr, uid, warehouse_id, - context=context) - warranty_address = claim_line_obj._warranty_return_address_values( - cr, uid, [], product, company, - warehouse, context=context) - values.update(warranty_address) - return values - - if create_lines: # happens when the invoice is changed - 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) - line = { - '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', - } - line.update(warranty_values(invoice_line.invoice_id, - invoice_line.product_id)) - claim_lines.append(line) - elif lines: # happens when the date, warehouse or claim type is - # modified - for command in lines: - code = command[0] - assert code != 6, "command 6 not supported in on_change" - if code in (0, 1, 4): - # 0: link a new record with values - # 1: update an existing record with values - # 4: link to existing record - line_id = command[1] - if code == 4: - code = 1 # we want now to update values - values = {} - else: - values = command[2] - invoice_line_id = values.get('invoice_line_id') - product_id = values.get('product_id') - if code == 1: # get the existing line - # if the fields have not changed, fallback - # on the database values - browse_line = claim_line_obj.read(cr, uid, - line_id, - ['invoice_line_id', - 'product_id'], - context=context) - if not invoice_line_id: - invoice_line_id = browse_line['invoice_line_id'][0] - if not product_id: - product_id = browse_line['product_id'][0] - - if invoice_line_id and product_id: - invoice_line = invoice_line_obj.browse(cr, uid, - invoice_line_id, - context=context) - product = product_obj.browse(cr, uid, product_id, - context=context) - values.update(warranty_values(invoice_line.invoice_id, - product)) - claim_lines.append((code, line_id, values)) - elif code in (2, 3, 5): - claim_lines.append(command) - - value = {'claim_line_ids': claim_lines} - delivery_address_id = False - if invoice_id: - invoice = invoice_obj.browse(cr, uid, invoice_id, context=context) - delivery_address_id = invoice.partner_id.id - value['delivery_address_id'] = delivery_address_id - - return {'value': value} - - def message_get_reply_to(self, cr, uid, ids, context=None): - """ Override to get the reply_to of the parent project. """ - return [claim.section_id.message_get_reply_to()[0] - if claim.section_id else False - for claim in self.browse(cr, SUPERUSER_ID, ids, - context=context)] - - def message_get_suggested_recipients(self, cr, uid, ids, context=None): - recipients = super(crm_claim, self - ).message_get_suggested_recipients(cr, uid, ids, - context=context) - try: - for claim in self.browse(cr, uid, ids, context=context): - if claim.partner_id: - self._message_add_suggested_recipient( - cr, uid, recipients, claim, - partner=claim.partner_id, reason=_('Customer')) - elif claim.email_from: - self._message_add_suggested_recipient( - cr, uid, recipients, claim, - email=claim.email_from, reason=_('Customer Email')) - except (osv.except_osv, orm.except_orm): - # no read access rights -> just ignore suggested recipients - # because this imply modifying followers - pass - return recipients diff --git a/__unported__/crm_claim_rma/crm_claim_rma_view.xml b/__unported__/crm_claim_rma/crm_claim_rma_view.xml deleted file mode 100644 index 65f4dc29..00000000 --- a/__unported__/crm_claim_rma/crm_claim_rma_view.xml +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - CRM - Claims Search - claim.line - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CRM - Claims Tree - claim.line - - - - - - - - - - - - - - - - - - - - - - - - CRM - Claim product return line Form - claim.line - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CRM - Claims Tree - crm.claim - - - - - - - - - - - - - CRM - Claims Form - crm.claim - - - - - - - - - - CRM - Claim product return Form - crm.claim - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CRM - Claim product return Form - crm.claim - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CRM - Claims Search - crm.claim - - - - - - - - - - - - - - - {"search_default_user_id":uid, "stage_type":'claim'} - - - - - Claim lines - claim.line - form - tree,form - - - - - - Claim line substates - substate.substate - form - - - - - - diff --git a/__unported__/crm_claim_rma/stock.py b/__unported__/crm_claim_rma/stock.py deleted file mode 100644 index ca8ab35e..00000000 --- a/__unported__/crm_claim_rma/stock.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Copyright 2013 Camptocamp -# Copyright 2009-2013 Akretion, -# Author: Emmanuel Samyn, Raphaël Valyi, Sébastien Beau, -# Benoît Guillot, Joel Grand-Guillaume -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero 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 Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -from openerp.osv import fields, orm - - -class stock_picking(orm.Model): - - _inherit = "stock.picking" - - _columns = { - 'claim_id': fields.many2one('crm.claim', 'Claim'), - } - - 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 = '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'), - } - - -class stock_picking_in(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 -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_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': - self.write(cr, uid, move_id, {'state': 'confirmed'}, - context=context) - return move_id diff --git a/__unported__/crm_claim_rma/stock_view.xml b/__unported__/crm_claim_rma/stock_view.xml deleted file mode 100644 index 38776688..00000000 --- a/__unported__/crm_claim_rma/stock_view.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - crm_claim_rma.picking_in_form - stock.picking.in - - - - - - - - - - crm_claim_rma.picking_out_form - stock.picking.out - - - - - - - - - - crm_claim_rma.picking_in_search - stock.picking.in - - - - - - - - - - - - crm_claim_rma.picking_out_search - stock.picking.out - - - - - - - - - - - - diff --git a/__unported__/crm_claim_rma/tests/test_lp_1282584.py b/__unported__/crm_claim_rma/tests/test_lp_1282584.py deleted file mode 100644 index d9e21500..00000000 --- a/__unported__/crm_claim_rma/tests/test_lp_1282584.py +++ /dev/null @@ -1,112 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Yannick Vaucher -# Copyright 2014 Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero 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 Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -from openerp.tests import common - - -class test_lp_1282584(common.TransactionCase): - """ Test wizard open the right type of view - - The wizard can generate picking.in and picking.out - Let's ensure it open the right view for each picking type - """ - - def setUp(self): - super(test_lp_1282584, self).setUp() - cr, uid = self.cr, self.uid - - self.WizardMakePicking = self.registry('claim_make_picking.wizard') - ClaimLine = self.registry('claim.line') - Claim = self.registry('crm.claim') - - self.product_id = self.ref('product.product_product_4') - - self.partner_id = self.ref('base.res_partner_12') - - # Create the claim with a claim line - self.claim_id = Claim.create( - cr, uid, - { - 'name': 'TEST CLAIM', - 'number': 'TEST CLAIM', - 'claim_type': 'customer', - 'delivery_address_id': self.partner_id, - }) - - claim = Claim.browse(cr, uid, self.claim_id) - self.warehouse_id = claim.warehouse_id.id - self.claim_line_id = ClaimLine.create( - cr, uid, - { - 'name': 'TEST CLAIM LINE', - 'claim_origine': 'none', - 'product_id': self.product_id, - 'claim_id': self.claim_id, - 'location_dest_id': claim.warehouse_id.lot_stock_id.id - }) - - def test_00(self): - """Test wizard opened view model for a new product return - - """ - cr, uid = self.cr, self.uid - wiz_context = { - 'active_id': self.claim_id, - 'partner_id': self.partner_id, - 'warehouse_id': self.warehouse_id, - 'picking_type': 'in', - } - wizard_id = self.WizardMakePicking.create(cr, uid, { - }, context=wiz_context) - - res = self.WizardMakePicking.action_create_picking( - cr, uid, [wizard_id], context=wiz_context) - self.assertEquals(res.get('res_model'), 'stock.picking.in', - "Wrong model defined") - - def test_01(self): - """Test wizard opened view model for a new delivery - - """ - - cr, uid = self.cr, self.uid - - WizardChangeProductQty = self.registry('stock.change.product.qty') - wiz_context = {'active_id': self.product_id} - wizard_chg_qty_id = WizardChangeProductQty.create(cr, uid, { - 'product_id': self.product_id, - 'new_quantity': 12}) - WizardChangeProductQty.change_product_qty(cr, uid, - [wizard_chg_qty_id], - context=wiz_context) - - wiz_context = { - 'active_id': self.claim_id, - 'partner_id': self.partner_id, - 'warehouse_id': self.warehouse_id, - 'picking_type': 'out', - } - wizard_id = self.WizardMakePicking.create(cr, uid, { - }, context=wiz_context) - - res = self.WizardMakePicking.action_create_picking( - cr, uid, [wizard_id], context=wiz_context) - self.assertEquals(res.get('res_model'), 'stock.picking.out', - "Wrong model defined") diff --git a/__unported__/crm_claim_rma/wizard/claim_make_picking.py b/__unported__/crm_claim_rma/wizard/claim_make_picking.py deleted file mode 100644 index 3acd8faa..00000000 --- a/__unported__/crm_claim_rma/wizard/claim_make_picking.py +++ /dev/null @@ -1,270 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Copyright 2013 Camptocamp -# Copyright 2009-2013 Akretion, -# Author: Emmanuel Samyn, Raphaël Valyi, Sébastien Beau, -# Benoît Guillot, Joel Grand-Guillaume -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero 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 Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -from openerp.osv import fields, orm -from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT -from openerp import netsvc -from openerp.tools.translate import _ -import time - - -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', - string='Source Location', - help="Location where the returned products are from.", - required=True), - '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_picking', - 'claim_picking_id', - 'claim_line_id', - 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 = {} - 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) - 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 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 = {} - 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, - ['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'], - ['property_stock_customer'], - context=context)['property_stock_customer'][0] - return loc_id - - def _get_common_dest_location_from_line(self, cr, uid, line_ids, context): - """Return the ID of the common location between all lines. If no common - destination was found, return False""" - loc_id = False - line_obj = self.pool.get('claim.line') - line_location = [] - 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: - loc_id = line_location[0] - return loc_id - - def _get_common_partner_from_line(self, cr, uid, line_ids, context): - """Return the ID of the common partner between all lines. If no common - partner was found, return False""" - partner_id = False - 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 - not in line_partner): - line_partner.append(line.warranty_return_partner.id) - 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 incoming shippment take the location_dest_id common to all - lines, or if different, return None.""" - 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'), - ['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 ! - 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) - return loc_id - - _defaults = { - 'claim_line_source_location': _get_source_loc, - 'claim_line_dest_location': _get_dest_loc, - 'claim_line_ids': _get_claim_lines, - } - - 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 = {} - view_obj = self.pool.get('ir.ui.view') - name = 'RMA picking out' - if context.get('picking_type') == 'out': - p_type = 'out' - write_field = 'move_out_id' - note = 'RMA picking out' - else: - p_type = 'in' - write_field = 'move_in_id' - if context.get('picking_type'): - note = 'RMA picking ' + str(context.get('picking_type')) - name = note - model = 'stock.picking.' + p_type - view_id = view_obj.search(cr, uid, - [('model', '=', model), - ('type', '=', 'form'), - ], - 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) - partner_id = claim.delivery_address_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) - if not common_dest_loc_id: - raise orm.except_orm( - _('Error !'), - _('A product return cannot be created for various ' - 'destination locations, please choose line with a ' - 'same destination location.')) - self.pool.get('claim.line').auto_set_warranty(cr, uid, - line_ids, - context=context) - common_dest_partner_id = self._get_common_partner_from_line( - cr, uid, line_ids, context=context) - if not 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, - }, - context=context) - # Create picking lines - fmt = DEFAULT_SERVER_DATETIME_FORMAT - for wizard_claim_line in wizard.claim_line_ids: - 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(fmt), - 'date_expected': time.strftime(fmt), - '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) - picking_obj.action_assign(cr, uid, [picking_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, - 'res_model': model, - 'res_id': picking_id, - 'type': 'ir.actions.act_window', - } - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/__unported__/crm_claim_rma/__init__.py b/crm_claim_rma/__init__.py similarity index 91% rename from __unported__/crm_claim_rma/__init__.py rename to crm_claim_rma/__init__.py index cf35d06f..0c21c9f9 100644 --- a/__unported__/crm_claim_rma/__init__.py +++ b/crm_claim_rma/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- ############################################################################## # +# Copyright 2015 Eezee-It # Copyright 2013 Camptocamp # Copyright 2009-2013 Akretion, # Author: Emmanuel Samyn, Raphaël Valyi, Sébastien Beau, @@ -20,7 +21,6 @@ # along with this program. If not, see . # ############################################################################## -from . import wizard -from . import crm_claim_rma -from . import account_invoice -from . import stock + +from . import models +from . import wizards diff --git a/__unported__/crm_claim_rma/__openerp__.py b/crm_claim_rma/__openerp__.py similarity index 79% rename from __unported__/crm_claim_rma/__openerp__.py rename to crm_claim_rma/__openerp__.py index c428aa0d..735e87ae 100644 --- a/__unported__/crm_claim_rma/__openerp__.py +++ b/crm_claim_rma/__openerp__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- ############################################################################## # +# Copyright 2015 Eezee-It # Copyright 2013 Camptocamp # Copyright 2009-2013 Akretion, # Author: Emmanuel Samyn, Raphaël Valyi, Sébastien Beau, @@ -20,6 +21,7 @@ # along with this program. If not, see . # ############################################################################## + { 'name': 'RMA Claim (Product Return Management)', 'version': '1.1', @@ -66,30 +68,36 @@ Contributors: * Joel Grand-Guillaume * Guewen Baconnier * Yannick Vaucher + * Javier Carrasco """, - 'author': "Akretion, Camptocamp,Odoo Community Association (OCA)", - 'website': 'http://www.akretion.com, http://www.camptocamp.com', + 'author': "Akretion, Camptocamp, Eezee-it, MONK Software" + "Odoo Community Association (OCA)", + 'website': 'http://www.akretion.com, http://www.camptocamp.com, ' + 'http://www.eezee-it.com, http://www.wearemonk.com', 'license': 'AGPL-3', - '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', - 'res_partner_view.xml', - 'crm_claim_rma_data.xml', - ], + 'depends': [ + 'sale', + 'stock', + 'crm_claim', + 'crm_claim_code', + 'product_warranty', + ], + 'data': [ + 'wizards/claim_make_picking.xml', + 'views/crm_claim_rma.xml', + 'views/account_invoice.xml', + 'views/res_partner.xml', + 'data/crm_claim_rma.xml', + 'security/ir.model.access.csv', + ], 'test': ['test/test_invoice_refund.yml'], - 'images': ['images/product_return.png', - 'images/claim.png', - 'images/return_line.png', - 'images/exchange.png', - ], + 'images': [ + 'images/product_return.png', + 'images/claim.png', + 'images/return_line.png', + 'images/exchange.png', + ], 'installable': True, 'auto_install': False, } diff --git a/__unported__/crm_claim_rma/crm_claim_rma_data.xml b/crm_claim_rma/data/crm_claim_rma.xml similarity index 94% rename from __unported__/crm_claim_rma/crm_claim_rma_data.xml rename to crm_claim_rma/data/crm_claim_rma.xml index 8272eb29..e964d910 100644 --- a/__unported__/crm_claim_rma/crm_claim_rma_data.xml +++ b/crm_claim_rma/data/crm_claim_rma.xml @@ -21,8 +21,7 @@ After Sales Service ASV - - + + + + CRM - Claims Search + claim.line + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CRM - Claims Tree + claim.line + + + + + + + + + + + + + + + + + + + + + + + + CRM - Claim product return line Form + claim.line + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Claim line form view to be used inside claim tree + primary + claim.line + + + + + 1 + + + {'claim_id': parent.id, 'company_id': parent.company_id, 'warehouse_id': parent.warehouse_id, 'claim_type': parent.claim_type, 'claim_date': parent.date} + + + {'claim_id': parent.id, 'company_id': parent.company_id, 'warehouse_id': parent.warehouse_id, 'claim_type': parent.claim_type, 'claim_date': parent.date} + + + + + + + CRM - Claims Tree + crm.claim + + + + + + + + + + + CRM - Claims Search + crm.claim + + + + + + + + + + + + + + + + + + + + + + + + + {"search_default_user_id":uid, "stage_type":'claim'} + + + + + Claim lines + claim.line + form + tree,form + + + + + + + Claim line substates + substate.substate + form + + + + + + + + CRM - Claim product return Form + crm.claim + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/__unported__/crm_claim_rma/res_partner_view.xml b/crm_claim_rma/views/res_partner.xml similarity index 94% rename from __unported__/crm_claim_rma/res_partner_view.xml rename to crm_claim_rma/views/res_partner.xml index ea4ac7b3..bea428fc 100644 --- a/__unported__/crm_claim_rma/res_partner_view.xml +++ b/crm_claim_rma/views/res_partner.xml @@ -15,7 +15,6 @@ - diff --git a/__unported__/crm_claim_rma/wizard/__init__.py b/crm_claim_rma/wizards/__init__.py similarity index 97% rename from __unported__/crm_claim_rma/wizard/__init__.py rename to crm_claim_rma/wizards/__init__.py index 93b4edf3..dc3e68a6 100644 --- a/__unported__/crm_claim_rma/wizard/__init__.py +++ b/crm_claim_rma/wizards/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- ############################################################################## # +# Copyright 2015 Eezee-It # Copyright 2013 Camptocamp # Copyright 2009-2013 Akretion, # Author: Emmanuel Samyn, Raphaël Valyi, Sébastien Beau, @@ -20,5 +21,6 @@ # along with this program. If not, see . # ############################################################################## + from . import claim_make_picking from . import account_invoice_refund diff --git a/__unported__/crm_claim_rma/wizard/account_invoice_refund.py b/crm_claim_rma/wizards/account_invoice_refund.py similarity index 58% rename from __unported__/crm_claim_rma/wizard/account_invoice_refund.py rename to crm_claim_rma/wizards/account_invoice_refund.py index 568e3774..729525bb 100644 --- a/__unported__/crm_claim_rma/wizard/account_invoice_refund.py +++ b/crm_claim_rma/wizards/account_invoice_refund.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- ############################################################################## # +# Copyright 2015 Eezee-It, MONK Software # Copyright 2013 Camptocamp # Copyright 2009-2013 Akretion, # Author: Emmanuel Samyn, Raphaël Valyi, Sébastien Beau, -# Benoît Guillot, Joel Grand-Guillaume +# Benoît Guillot, Joel Grand-Guillaume, Leonardo Donelli # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -20,27 +21,21 @@ # along with this program. If not, see . # ############################################################################## -from openerp.osv import orm + +from openerp import models, fields, api -class account_invoice_refund(orm.TransientModel): - +class AccountInvoiceRefund(models.TransientModel): _inherit = "account.invoice.refund" - def compute_refund(self, cr, uid, ids, mode='refund', context=None): - 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=mode, context=context) + def _default_description(self): + return self.env.context.get('description', '') - def _get_description(self, cr, uid, context=None): - if context is None: - context = {} - description = context.get('description') or '' - return description + description = fields.Char(default=_default_description) - _defaults = { - 'description': _get_description, - } + @api.one + def compute_refund(self, mode='refund'): + invoice_ids = self.env.context.get('invoice_ids', []) + if invoice_ids: + self = self.with_context(active_ids=invoice_ids) + return super(AccountInvoiceRefund, self).compute_refund(mode=mode) diff --git a/crm_claim_rma/wizards/claim_make_picking.py b/crm_claim_rma/wizards/claim_make_picking.py new file mode 100644 index 00000000..c2fcc6a3 --- /dev/null +++ b/crm_claim_rma/wizards/claim_make_picking.py @@ -0,0 +1,237 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright 2015 Eezee-It, MONK Software +# Copyright 2013 Camptocamp +# Copyright 2009-2013 Akretion, +# Author: Emmanuel Samyn, Raphaël Valyi, Sébastien Beau, +# Benoît Guillot, Joel Grand-Guillaume, Leonardo Donelli +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +import time + +from openerp import models, fields, exceptions, api, workflow, _ +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DT_FORMAT + + +class ClaimMakePicking(models.TransientModel): + _name = 'claim_make_picking.wizard' + _description = 'Wizard to create pickings from claim lines' + + @api.returns('stock.location') + def _get_common_dest_location_from_line(self, lines): + """ + If all the lines have the same destination location return that, + else return an empty recordset + """ + dests = lines.mapped('location_dest_id') + return dests[0] if len(dests) == 1 else self.env['stock.location'] + + @api.returns('res.partner') + def _get_common_partner_from_line(self, lines): + """ + If all the lines have the same warranty return partner return that, + else return an empty recordset + """ + partners = lines.mapped('warranty_return_partner') + return partners[0] if len(partners) == 1 else self.env['res.partner'] + + def _default_claim_line_source_location_id(self): + picking_type = self.env.context.get('picking_type') + partner_id = self.env.context.get('partner_id') + warehouse_id = self.env.context.get('warehouse_id') + + if picking_type == 'out' and warehouse_id: + return self.env['stock.warehouse'].browse( + warehouse_id).lot_stock_id + + if partner_id: + partner = self.env['res.partner'].browse(partner_id) + return partner.property_stock_customer + + # return empty recordset, see https://github.com/odoo/odoo/issues/4384 + return self.env['stock.location'] + + def _default_claim_line_dest_location_id(self): + """Return the location_id to use as destination. + + If it's an outoing shipment: take the customer stock property + If it's an incoming shipment take the location_dest_id common to all + lines, or if different, return None. + """ + picking_type = self.env.context.get('picking_type') + partner_id = self.env.context.get('partner_id') + + if picking_type == 'out' and partner_id: + return self.env['res.partner'].browse( + partner_id).property_stock_customer + + if picking_type == 'in' and partner_id: + # Add the case of return to supplier ! + lines = self._default_claim_line_ids() + return self._get_common_dest_location_from_line(lines) + + # return empty recordset, see https://github.com/odoo/odoo/issues/4384 + return self.env['stock.location'] + + @api.returns('claim.line') + def _default_claim_line_ids(self): + # TODO use custom states to show buttons of this wizard or not instead + # of raise an error + move_field = ('move_out_id' + if self.env.context.get('picking_type') == 'out' + else 'move_in_id') + domain = [ + ('claim_id', '=', self.env.context['active_id']), + '|', (move_field, '=', False), (move_field+'.state', '=', 'cancel') + ] + lines = self.env['claim.line'].search(domain) + if not lines: + raise exceptions.Warning( + _('Error'), + _('A picking has already been created for this claim.')) + return lines + + claim_line_source_location_id = fields.Many2one( + 'stock.location', string='Source Location', required=True, + default=_default_claim_line_source_location_id, + help="Location where the returned products are from.") + claim_line_dest_location_id = fields.Many2one( + 'stock.location', string='Dest. Location', required=True, + default=_default_claim_line_dest_location_id, + help="Location where the system will stock the returned products.") + claim_line_ids = fields.Many2many( + 'claim.line', + 'claim_line_picking', + 'claim_picking_id', + 'claim_line_id', + string='Claim lines', default=_default_claim_line_ids) + + def _get_picking_name(self): + return 'RMA picking {}'.format( + self.env.context.get('picking_type', 'in')) + + def _get_picking_note(self): + return self._get_picking_name() + + def _get_picking_data(self, claim, picking_type, partner_id): + return { + 'origin': claim.code, + 'picking_type_id': picking_type.id, + 'move_type': 'one', # direct + 'state': 'draft', + 'date': time.strftime(DT_FORMAT), + 'partner_id': partner_id, + 'invoice_state': "none", + 'company_id': claim.company_id.id, + 'location_id': self.claim_line_source_location_id.id, + 'location_dest_id': self.claim_line_dest_location_id.id, + 'note': self._get_picking_note(), + 'claim_id': claim.id, + } + + def _get_picking_line_data(self, claim, picking, line): + return { + 'name': line.product_id.name_template, + 'priority': '0', + 'date': time.strftime(DT_FORMAT), + 'date_expected': time.strftime(DT_FORMAT), + 'product_id': line.product_id.id, + 'product_uom_qty': line.product_returned_quantity, + 'product_uom': line.product_id.product_tmpl_id.uom_id.id, + 'partner_id': claim.delivery_address_id.id, + 'picking_id': picking.id, + 'state': 'draft', + 'price_unit': line.unit_sale_price, + 'company_id': claim.company_id.id, + 'location_id': self.claim_line_source_location_id.id, + 'location_dest_id': self.claim_line_dest_location_id.id, + 'note': self._get_picking_note(), + } + + @api.multi + def action_create_picking(self): + + picking_type_code = 'incoming' + write_field = 'move_out_id' + if self.env.context.get('picking_type') == 'out': + picking_type_code = 'outgoing' + write_field = 'move_in_id' + destination_id = self.claim_line_dest_location_id.id + picking_type = self.env['stock.picking.type'].search([ + ('code', '=', picking_type_code), + ('default_location_dest_id', '=', destination_id) + ], limit=1) + + claim = self.env['crm.claim'].browse(self.env.context['active_id']) + partner_id = claim.delivery_address_id.id + claim_lines = self.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 self.env.context.get('product_return'): + common_dest_location = self._get_common_dest_location_from_line( + claim_lines) + if not common_dest_location: + raise Warning( + _('Error'), + _('A product return cannot be created for various ' + 'destination locations, please choose line with a ' + 'same destination location.')) + + claim_lines.auto_set_warranty() + + common_dest_partner = self._get_common_partner_from_line( + claim_lines) + if not common_dest_partner: + raise exceptions.Warning( + _('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 = self.env['stock.picking'].create( + self._get_picking_data(claim, picking_type, partner_id)) + + # Create picking lines + for line in self.claim_line_ids: + move = self.env['stock.move'].create( + self._get_picking_line_data(claim, picking, line)) + line.write({write_field: move.id}) + + wf_service = workflow + if picking: + cr, uid = self.env.cr, self.env.uid + wf_service.trg_validate(uid, 'stock.picking', + picking.id, 'button_confirm', cr) + picking.action_assign() + + domain = [ + ('picking_type_id.code', '=', picking_type_code), + ('partner_id', '=', partner_id) + ] + return { + 'name': self._get_picking_name(), + 'view_type': 'form', + 'view_mode': 'form', + 'domain': domain, + 'res_model': 'stock.picking', + 'res_id': picking.id, + 'type': 'ir.actions.act_window', + } diff --git a/__unported__/crm_claim_rma/wizard/claim_make_picking_view.xml b/crm_claim_rma/wizards/claim_make_picking.xml similarity index 77% rename from __unported__/crm_claim_rma/wizard/claim_make_picking_view.xml rename to crm_claim_rma/wizards/claim_make_picking.xml index be499a4f..804b36c8 100644 --- a/__unported__/crm_claim_rma/wizard/claim_make_picking_view.xml +++ b/crm_claim_rma/wizards/claim_make_picking.xml @@ -1,6 +1,7 @@ @@ -12,15 +13,19 @@ claim_make_picking.wizard - - - - - + + + + + +