mirror of
https://github.com/OCA/rma.git
synced 2025-02-16 17:11:47 +02:00
Merge pull request #32 from eezee-it/port_crm_claim_rma
Port crm_claim_rma
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
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
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
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
|
||||
@@ -1,418 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- Return line -->
|
||||
<!-- SEARCH -->
|
||||
<record id="view_crm_claim_lines_filter" model="ir.ui.view">
|
||||
<field name="name">CRM - Claims Search</field>
|
||||
<field name="model">claim.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Claims">
|
||||
<filter icon="terp-check" string="Current" name="current"
|
||||
domain="[('state','in',('draft', 'refused', 'treated'))]"
|
||||
separator="1" help="Draft and Open Claims" />
|
||||
<filter icon="terp-camera_test"
|
||||
string="In Progress"
|
||||
domain="[('state','in',('confirmed','in_to_control','in_to_treate'))]"
|
||||
separator="1" help="In Progress Claims"/>
|
||||
<separator orientation="vertical"/>
|
||||
<field name="state" select='1'/>
|
||||
<field name="substate_id" select='1'/>
|
||||
<field name="name" select='1'/>
|
||||
<field name="warning" select='1'/>
|
||||
<field name="invoice_line_id" select='1'/>
|
||||
<field name="product_id" select='1'/>
|
||||
<field name="prodlot_id" select='1'/>
|
||||
<newline/>
|
||||
<group expand="0" string="More">
|
||||
<field name="last_state_change" select='1'/>
|
||||
<field name="guarantee_limit" select='1'/>
|
||||
<field name="return_value" select='1'/>
|
||||
<field name="name" select='1'/>
|
||||
</group>
|
||||
<newline/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Invoice" icon="terp-dolar"
|
||||
domain="[]" help="Invoice"
|
||||
context="{'group_by':'invoice_id'}" />
|
||||
<filter string="Product" icon="terp-product"
|
||||
domain="[]" help="Product"
|
||||
context="{'group_by':'product_id'}" />
|
||||
<separator orientation="vertical"/>
|
||||
<filter string="Substate" icon="terp-stage"
|
||||
domain="[]" context="{'group_by':'substate_id'}" />
|
||||
<filter string="Claim n°" icon="terp-emblem-documents"
|
||||
domain="[]" context="{'group_by':'claim_id'}" />
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- TREE -->
|
||||
<record model="ir.ui.view" id="crm_claim_line_tree_view">
|
||||
<field name="name">CRM - Claims Tree</field>
|
||||
<field name="model">claim.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Returned lines">
|
||||
<field name="claim_id" invisible="1"/>
|
||||
<field name="state"/>
|
||||
<field name="substate_id"/>
|
||||
<field name="product_id"/>
|
||||
<field name="name"/>
|
||||
<field name="prodlot_id"/>
|
||||
<field name="warning"/>
|
||||
<field name="warranty_type"/>
|
||||
<field name="warranty_return_partner"/>
|
||||
<button name="set_warranty" string="Compute Waranty" type="object" icon="gtk-justify-fill"/>
|
||||
<field name="product_returned_quantity"/>
|
||||
<field name="claim_origine"/>
|
||||
<field name="refund_line_id"/>
|
||||
<field name="move_in_id"/>
|
||||
<field name="move_out_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- FORM -->
|
||||
<record model="ir.ui.view" id="crm_claim_line_form_view">
|
||||
<field name="name">CRM - Claim product return line Form</field>
|
||||
<field name="model">claim.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Claim Line" version="7.0">
|
||||
<header>
|
||||
<button name="set_warranty" string="Calculate warranty state" type="object" class="oe_highlight"/>
|
||||
</header>
|
||||
<sheet string="Claims">
|
||||
<div class="oe_title">
|
||||
<group>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
</group>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Returned good">
|
||||
<field name="product_returned_quantity"/>
|
||||
<field name="product_id"
|
||||
on_change="onchange_product_id(product_id, invoice_line_id, claim_id, False, False, False, False, context)"/>
|
||||
<field name="prodlot_id"/>
|
||||
<field name="unit_sale_price"/>
|
||||
<field name="return_value"/>
|
||||
</group>
|
||||
<group string="Linked Document">
|
||||
<field name="claim_id" />
|
||||
<field name="invoice_line_id"
|
||||
on_change="onchange_product_id(product_id, invoice_line_id, claim_id, False, False, False, False, context)"/>
|
||||
<field name="refund_line_id"/>
|
||||
<field name="move_in_id"/>
|
||||
<field name="move_out_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Problem">
|
||||
<field name="claim_origine" nolabel="1" colspan="4"/>
|
||||
<field name="claim_descr" nolabel="1" colspan="4"/>
|
||||
</group>
|
||||
<group string="Warranty">
|
||||
<field name="guarantee_limit"/>
|
||||
<field name="warning"/>
|
||||
<field name="warranty_return_partner"/>
|
||||
<field name="warranty_type"/>
|
||||
</group>
|
||||
</group>
|
||||
<separator string="State" colspan="4"/>
|
||||
<group col="6" colspan="4">
|
||||
<field name="state"/>
|
||||
<field name="substate_id" widget='selection' />
|
||||
<field name="last_state_change"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- CLAIM VIEWS -->
|
||||
|
||||
<record model="ir.ui.view" id="crm_case_claims_tree_view">
|
||||
<field name="name">CRM - Claims Tree</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="inherit_id" ref="crm_claim.crm_case_claims_tree_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="stage_id" position="after">
|
||||
<field name="section_id" />
|
||||
</field>
|
||||
<field name="name" position="before">
|
||||
<field name="number" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="crm_case_claims_form_view_replace">
|
||||
<field name="name">CRM - Claims Form</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="inherit_id" ref="crm_claim.crm_case_claims_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'crm.claim')]" position="replace">
|
||||
<field name="categ_id" />
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="crm_claim_rma_form_view">
|
||||
<field name="name">CRM - Claim product return Form</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="inherit_id" ref="crm_claim.crm_case_claims_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<page string="Follow Up" position="before">
|
||||
<page string="Product Return">
|
||||
<group name="Product Return">
|
||||
<separator string="Product Return" colspan="4"/>
|
||||
<group>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="invoice_id" on_change="onchange_invoice_id(invoice_id, warehouse_id, claim_type, date, company_id, claim_line_ids, True, context)" domain="['|',('commercial_partner_id','=',partner_id),('partner_id','=',partner_id)]" />
|
||||
<field name="delivery_address_id" context="{'tree_view_ref': 'crm_claim_rma.view_partner_contact_tree', 'search_default_parent_id': partner_id}"/>
|
||||
</group>
|
||||
<group>
|
||||
<!-- Place for mass return button from crm_rma_lot_mass_return -->
|
||||
</group>
|
||||
<field name="claim_line_ids" nolabel="1" colspan="4" editable="top">
|
||||
<form string="Claim Line" version="7.0">
|
||||
<header>
|
||||
<button name="set_warranty" string="Calculate warranty state" type="object" class="oe_highlight"/>
|
||||
</header>
|
||||
<sheet string="Claims">
|
||||
<div class="oe_title">
|
||||
<group>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
</group>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Returned good">
|
||||
<field name="product_returned_quantity"/>
|
||||
<field name="product_id"
|
||||
on_change="onchange_product_id(product_id, invoice_line_id, False, parent.company_id, parent.warehouse_id, parent.claim_type, parent.date, context)"/>
|
||||
<field name="prodlot_id"/>
|
||||
<field name="unit_sale_price"/>
|
||||
<field name="return_value"/>
|
||||
</group>
|
||||
<group string="Linked Document">
|
||||
<field name="invoice_line_id"
|
||||
on_change="onchange_product_id(product_id, invoice_line_id, False, parent.company_id, parent.warehouse_id, parent.claim_type, parent.date, context)"/>
|
||||
<field name="refund_line_id"/>
|
||||
<field name="move_in_id"/>
|
||||
<field name="move_out_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Problem">
|
||||
<field name="claim_origine" nolabel="1" colspan="4"/>
|
||||
<field name="claim_descr" nolabel="1" colspan="4"/>
|
||||
</group>
|
||||
<group string="Warranty">
|
||||
<field name="guarantee_limit"/>
|
||||
<field name="warning"/>
|
||||
<field name="warranty_return_partner"/>
|
||||
<field name="warranty_type"/>
|
||||
</group>
|
||||
</group>
|
||||
<separator string="State" colspan="4"/>
|
||||
<group col="6" colspan="4">
|
||||
<field name="state"/>
|
||||
<field name="substate_id" widget='selection' />
|
||||
<field name="last_state_change"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</group>
|
||||
<group col="4" colspan="4" attrs="{'invisible':[('state', '<>','open')]}">
|
||||
<separator string="Action" colspan="4" />
|
||||
<button name="%(action_claim_picking_in)d"
|
||||
string="New Products Return" states="open"
|
||||
type="action" target="new"
|
||||
context="{'warehouse_id': warehouse_id,
|
||||
'partner_id': partner_id}"/>
|
||||
|
||||
<button name="%(action_claim_picking_out)d"
|
||||
string="New Delivery" states="open"
|
||||
type="action" target="new"
|
||||
context="{'warehouse_id': warehouse_id,
|
||||
'partner_id': partner_id}"/>
|
||||
|
||||
<button name="%(account.action_account_invoice_refund)d"
|
||||
type='action' string='New Refund'
|
||||
states='open' icon="gtk-execute"
|
||||
context="{
|
||||
'invoice_ids': [invoice_id],
|
||||
'claim_line_ids': claim_line_ids,
|
||||
'description': name,
|
||||
'claim_id': id,
|
||||
}"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Generated Documents">
|
||||
<separator colspan="2" string="Refunds"/>
|
||||
<field name="invoice_ids" colspan="4" readonly="1"/>
|
||||
<separator colspan="2" string="Receptions / Deliveries"/>
|
||||
<field name="picking_ids" colspan="4" readonly="1"/>
|
||||
</page>
|
||||
</page>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Right side link to orders -->
|
||||
<act_window
|
||||
id="act_crm_claim_rma_sale_orders"
|
||||
name="Quotations and Sales"
|
||||
res_model="sale.order"
|
||||
src_model="crm.claim"/>
|
||||
<!-- Right side link to invoices -->
|
||||
<act_window
|
||||
domain="[('type', '=', 'out_invoice')]"
|
||||
id="act_crm_claim_rma_invoice_out"
|
||||
name="Customer Invoices"
|
||||
res_model="account.invoice"
|
||||
src_model="crm.claim"/>
|
||||
<!-- Right side link to invoices -->
|
||||
<act_window
|
||||
domain="[('type', '=', 'in_invoice')]"
|
||||
id="act_crm_claim_rma_invoice_in"
|
||||
name="Supplier Invoices"
|
||||
res_model="account.invoice"
|
||||
src_model="crm.claim"/>
|
||||
<!-- Right side link to refunds -->
|
||||
<act_window
|
||||
domain="[('type', '=', 'out_refund')]"
|
||||
id="act_crm_claim_rma_refunds_out"
|
||||
name="Customer Refunds"
|
||||
res_model="account.invoice"
|
||||
src_model="crm.claim"/>
|
||||
<!-- Right side link to refunds -->
|
||||
<act_window
|
||||
domain="[('type', '=', 'in_refund')]"
|
||||
id="act_crm_claim_rma_refunds_in"
|
||||
name="Supplier Refunds"
|
||||
res_model="account.invoice"
|
||||
src_model="crm.claim"/>
|
||||
<!-- Right side link to picking in -->
|
||||
<act_window
|
||||
domain="[('type', '=', 'in')]"
|
||||
id="act_crm_claim_rma_picking_in"
|
||||
name="Incoming Shipment and Returns"
|
||||
res_model="stock.picking.in"
|
||||
src_model="crm.claim"/>
|
||||
<!-- Right side link to picking out -->
|
||||
<act_window
|
||||
domain="[('type', '=', 'out')]"
|
||||
id="act_crm_claim_rma_picking_out"
|
||||
name="Deliveries"
|
||||
res_model="stock.picking.out"
|
||||
src_model="crm.claim"/>
|
||||
|
||||
<record model="ir.ui.view" id="crm_claim_rma_form_view2">
|
||||
<field name="name">CRM - Claim product return Form</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="inherit_id" ref="crm_claim.crm_case_claims_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="date_deadline" position="after">
|
||||
<field name="claim_type" on_change="onchange_invoice_id(invoice_id, warehouse_id, claim_type, date, company_id, claim_line_ids, False, context)"/>
|
||||
<field name="warehouse_id" on_change="onchange_invoice_id(invoice_id, warehouse_id, claim_type, date, company_id, claim_line_ids, False, context)"/>
|
||||
</field>
|
||||
<field name="name" position="replace">
|
||||
<div class="oe_title">
|
||||
<label for="number" class="oe_edit_only"/>
|
||||
<h1><field name="number"/></h1>
|
||||
</div>
|
||||
</field>
|
||||
<field name="date" position="replace">
|
||||
</field>
|
||||
<field name="user_id" position="before">
|
||||
<field name="name" />
|
||||
<field name="date" on_change="onchange_invoice_id(invoice_id, warehouse_id, claim_type, date, company_id, claim_line_ids, False, context)"/>
|
||||
</field>
|
||||
<xpath expr="//sheet[@string='Claims']/group[1]" position="inside">
|
||||
<div class="oe_right oe_button_box" name="buttons">
|
||||
<button name="%(act_crm_claim_rma_sale_orders)d" type="action"
|
||||
string="Quotations and Sales"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
|
||||
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"
|
||||
/>
|
||||
<button name="%(act_crm_claim_rma_invoice_out)d" type="action"
|
||||
string="Customer Invoices"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
|
||||
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"
|
||||
/>
|
||||
<button name="%(act_crm_claim_rma_refunds_out)d" type="action"
|
||||
string="Customer Refunds"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
|
||||
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"
|
||||
/>
|
||||
<button name="%(act_crm_claim_rma_invoice_in)d" type="action"
|
||||
string="Supplier Invoices"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['customer','other'])]}"
|
||||
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"
|
||||
/>
|
||||
<button name="%(act_crm_claim_rma_refunds_in)d" type="action"
|
||||
string="Supplier Refunds"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['customer','other'])]}"
|
||||
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"
|
||||
/>
|
||||
<button name="%(act_crm_claim_rma_picking_in)d" type="action"
|
||||
string="Returned Products"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
|
||||
context="{'search_default_claim_id': active_id,'search_default_user_id':False}"
|
||||
/>
|
||||
<button name="%(act_crm_claim_rma_picking_out)d" type="action"
|
||||
string="Deliveries"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
|
||||
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"
|
||||
/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Crm claim Search view -->
|
||||
<record id="view_crm_case_claims_filter" model="ir.ui.view">
|
||||
<field name="name">CRM - Claims Search</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="inherit_id" ref="crm_claim.view_crm_case_claims_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" string="Claims" position="before">
|
||||
<field name="number"/>
|
||||
</field>
|
||||
<filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}" position="before">
|
||||
<filter string="Sales Team" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'section_id'}"/>
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Menu -->
|
||||
<record model="ir.actions.act_window" id="crm_claim.crm_case_categ_claim0">
|
||||
<field name="context">{"search_default_user_id":uid, "stage_type":'claim'}</field>
|
||||
</record>
|
||||
|
||||
<!-- return lines action -->
|
||||
<record model="ir.actions.act_window" id="act_crm_case_claim_lines">
|
||||
<field name="name">Claim lines</field>
|
||||
<field name="res_model">claim.line</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="crm_claim_line_tree_view"/>
|
||||
<field name="search_view_id" ref="view_crm_claim_lines_filter"/>
|
||||
</record>
|
||||
<!-- substates action -->
|
||||
<record id="act_crm_claim_substates" model="ir.actions.act_window">
|
||||
<field name="name">Claim line substates</field>
|
||||
<field name="res_model">substate.substate</field>
|
||||
<field name="view_type">form</field>
|
||||
</record>
|
||||
<!-- Menu -->
|
||||
<menuitem name="Return lines" id="menu_crm_case_claims_claim_lines"
|
||||
parent="base.menu_aftersale" action="act_crm_case_claim_lines" sequence="2"/>
|
||||
<menuitem name="Returned line substates" id="menu_crm_case_claims_claim_line_substates"
|
||||
parent="crm_claim.menu_config_claim" action="act_crm_claim_substates" sequence="2"/>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
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
|
||||
@@ -1,55 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- INHERITED VIEW FOR THE OBJECT : stock_picking -->
|
||||
|
||||
<record id="picking_in_form" model="ir.ui.view">
|
||||
<field name="name">crm_claim_rma.picking_in_form</field>
|
||||
<field name="model">stock.picking.in</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_in_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='move_type']" position="after">
|
||||
<field name="claim_id" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="picking_out_form" model="ir.ui.view">
|
||||
<field name="name">crm_claim_rma.picking_out_form</field>
|
||||
<field name="model">stock.picking.out</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_out_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/form/sheet/notebook/page/group/group/field[@name='move_type']" position="after">
|
||||
<field name="claim_id" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_picking_in_search" model="ir.ui.view">
|
||||
<field name="name">crm_claim_rma.picking_in_search</field>
|
||||
<field name="model">stock.picking.in</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_in_search" />
|
||||
<field name="arch" type="xml">
|
||||
<filter string="To Invoice" name="to_invoice" icon="terp-dolar" domain="[('invoice_state', '=', '2binvoiced')]" position="after">
|
||||
<separator/>
|
||||
<filter string="RMA" icon="terp-accessories-archiver-minus" domain="[('claim_id', '!=', 'False')]" />
|
||||
<field name="claim_id" string="RMA" invisible="True"/>
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_picking_out_search" model="ir.ui.view">
|
||||
<field name="name">crm_claim_rma.picking_out_search</field>
|
||||
<field name="model">stock.picking.out</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_out_search" />
|
||||
<field name="arch" type="xml">
|
||||
<filter icon="terp-dolar" name="to_invoice" string="To Invoice" domain="[('invoice_state','=','2binvoiced')]" help="Delivery orders to invoice" position="after">
|
||||
<separator/>
|
||||
<filter string="RMA" icon="terp-accessories-archiver-minus" domain="[('claim_id', '!=', 'False')]" />
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
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")
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
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:
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from . import wizard
|
||||
from . import crm_claim_rma
|
||||
from . import account_invoice
|
||||
from . import stock
|
||||
|
||||
from . import models
|
||||
from . import wizards
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
{
|
||||
'name': 'RMA Claim (Product Return Management)',
|
||||
'version': '1.1',
|
||||
@@ -66,30 +68,36 @@ Contributors:
|
||||
* Joel Grand-Guillaume <joel.grandguillaume@camptocamp.com>
|
||||
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
|
||||
* Yannick Vaucher <yannick.vaucher@camptocamp.com>
|
||||
* Javier Carrasco <javier.carrasco@eezee-it.com>
|
||||
|
||||
""",
|
||||
'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,
|
||||
}
|
||||
@@ -21,8 +21,7 @@
|
||||
<record model="crm.case.section" id="section_after_sales_service">
|
||||
<field name="name">After Sales Service</field>
|
||||
<field name="code">ASV</field>
|
||||
<field name="parent_id" ref="crm.section_sales_department"/>
|
||||
<!-- <field name="stage_ids" eval="[(4, ref('crm_claim.stage_claim1')), (4, ref('crm_claim.stage_claim2')), (4, ref('crm_claim.stage_claim3')), (4, ref('crm_claim.stage_claim5'))]"/> -->
|
||||
<field name="parent_id" ref="sales_team.section_sales_department"/>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
@@ -562,11 +562,6 @@ msgstr ""
|
||||
msgid "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,..."
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: sql_constraint:crm.claim:0
|
||||
msgid "Number/Reference must be unique per Company!"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: code:addons/crm_claim_rma/crm_claim_rma.py:313
|
||||
#, python-format
|
||||
@@ -625,11 +620,6 @@ msgstr ""
|
||||
msgid "Merchandise Not Received"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: help:crm.claim,number:0
|
||||
msgid "Company internal claim unique number"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: model:ir.ui.menu,name:crm_claim_rma.menu_crm_case_claims_claim_line_substates
|
||||
msgid "Returned line substates"
|
||||
@@ -652,12 +642,6 @@ msgstr ""
|
||||
msgid "Source Location"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: field:crm.claim,number:0
|
||||
#: model:ir.model.fields,field_description:crm_claim_rma.field_crm_claim_number
|
||||
msgid "Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: model:ir.actions.act_window,name:crm_claim_rma.act_crm_claim_rma_picking_out
|
||||
msgid "Deliveries"
|
||||
@@ -599,11 +599,6 @@ msgstr ""
|
||||
"automático, ya que no tiene en cuenta facturas rectificativas previas, "
|
||||
"descuento de facturas, puede ser 0 si el producto es gratuito, etc..."
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: sql_constraint:crm.claim:0
|
||||
msgid "Number/Reference must be unique per Company!"
|
||||
msgstr "El número/referencia debe ser único por compañía"
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: code:addons/crm_claim_rma/crm_claim_rma.py:313
|
||||
#, python-format
|
||||
@@ -662,11 +657,6 @@ msgstr "Tratado"
|
||||
msgid "Merchandise Not Received"
|
||||
msgstr "Mercancía no recibida"
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: help:crm.claim,number:0
|
||||
msgid "Company internal claim unique number"
|
||||
msgstr "Número único de reclamación interno en la compañia"
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: model:ir.ui.menu,name:crm_claim_rma.menu_crm_case_claims_claim_line_substates
|
||||
msgid "Returned line substates"
|
||||
@@ -689,12 +679,6 @@ msgstr "Facturas de cliente"
|
||||
msgid "Source Location"
|
||||
msgstr "Localización de origen"
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: field:crm.claim,number:0
|
||||
#: model:ir.model.fields,field_description:crm_claim_rma.field_crm_claim_number
|
||||
msgid "Number"
|
||||
msgstr "Número"
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: model:ir.actions.act_window,name:crm_claim_rma.act_crm_claim_rma_picking_out
|
||||
msgid "Deliveries"
|
||||
@@ -592,11 +592,6 @@ msgstr ""
|
||||
"valeurs automatiques récupérée différentes des précédents avoir, réductions "
|
||||
"sur la facture,..."
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: sql_constraint:crm.claim:0
|
||||
msgid "Number/Reference must be unique per Company!"
|
||||
msgstr "La référence doit être unique par société !"
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: code:addons/crm_claim_rma/crm_claim_rma.py:313
|
||||
#, python-format
|
||||
@@ -655,11 +650,6 @@ msgstr "Traité"
|
||||
msgid "Merchandise Not Received"
|
||||
msgstr "Marchandise non reçue"
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: help:crm.claim,number:0
|
||||
msgid "Company internal claim unique number"
|
||||
msgstr "Numéro unique de réclamation interne à la société"
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: model:ir.ui.menu,name:crm_claim_rma.menu_crm_case_claims_claim_line_substates
|
||||
msgid "Returned line substates"
|
||||
@@ -682,12 +672,6 @@ msgstr "Factures client"
|
||||
msgid "Source Location"
|
||||
msgstr "Emplacement source"
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: field:crm.claim,number:0
|
||||
#: model:ir.model.fields,field_description:crm_claim_rma.field_crm_claim_number
|
||||
msgid "Number"
|
||||
msgstr "Numéro"
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: model:ir.actions.act_window,name:crm_claim_rma.act_crm_claim_rma_picking_out
|
||||
msgid "Deliveries"
|
||||
@@ -581,11 +581,6 @@ msgid ""
|
||||
"free,..."
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: sql_constraint:crm.claim:0
|
||||
msgid "Number/Reference must be unique per Company!"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: code:addons/crm_claim_rma/crm_claim_rma.py:313
|
||||
#, python-format
|
||||
@@ -644,11 +639,6 @@ msgstr ""
|
||||
msgid "Merchandise Not Received"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: help:crm.claim,number:0
|
||||
msgid "Company internal claim unique number"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: model:ir.ui.menu,name:crm_claim_rma.menu_crm_case_claims_claim_line_substates
|
||||
msgid "Returned line substates"
|
||||
@@ -671,12 +661,6 @@ msgstr ""
|
||||
msgid "Source Location"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: field:crm.claim,number:0
|
||||
#: model:ir.model.fields,field_description:crm_claim_rma.field_crm_claim_number
|
||||
msgid "Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_claim_rma
|
||||
#: model:ir.actions.act_window,name:crm_claim_rma.act_crm_claim_rma_picking_out
|
||||
msgid "Deliveries"
|
||||
3
crm_claim_rma/models/__init__.py
Normal file
3
crm_claim_rma/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import crm_claim_rma
|
||||
from . import account_invoice
|
||||
from . import stock
|
||||
101
crm_claim_rma/models/account_invoice.py
Normal file
101
crm_claim_rma/models/account_invoice.py
Normal file
@@ -0,0 +1,101 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import models, fields, api, exceptions
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class AccountInvoice(models.Model):
|
||||
_inherit = "account.invoice"
|
||||
|
||||
claim_id = fields.Many2one('crm.claim', string='Claim')
|
||||
|
||||
@api.model
|
||||
def _refund_cleanup_lines(self, lines):
|
||||
"""
|
||||
Override when from claim to update the quantity and link to the
|
||||
claim line.
|
||||
"""
|
||||
# check if is an invoice_line and we are from a claim
|
||||
if not (self.env.context.get('claim_line_ids') and lines and
|
||||
lines[0]._name == 'account.invoice.line'):
|
||||
return super(AccountInvoice, self)._refund_cleanup_lines(lines)
|
||||
|
||||
# start by browsing all the lines so that Odoo will correctly prefetch
|
||||
line_ids = [l[1] for l in self.env.context['claim_line_ids']]
|
||||
claim_lines = self.env['claim.line'].browse(line_ids)
|
||||
|
||||
new_lines = []
|
||||
for claim_line in claim_lines:
|
||||
if not claim_line.refund_line_id:
|
||||
# For each lines replace quantity and add claim_line_id
|
||||
inv_line = claim_line.invoice_line_id
|
||||
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_ids = inv_line[field_name].ids
|
||||
clean_line[field_name] = [(6, 0, tax_ids)]
|
||||
clean_line['quantity'] = claim_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 exceptions.Warning(
|
||||
_('A refund has already been created for this claim !'))
|
||||
return [(0, 0, l) for l in new_lines]
|
||||
|
||||
@api.model
|
||||
def _prepare_refund(self, invoice, date=None, period_id=None,
|
||||
description=None, journal_id=None):
|
||||
result = super(AccountInvoice, self)._prepare_refund(
|
||||
invoice, date=date, period_id=period_id, description=description,
|
||||
journal_id=journal_id)
|
||||
|
||||
if self.env.context.get('claim_id'):
|
||||
result['claim_id'] = self.env.context['claim_id']
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class AccountInvoiceLine(models.Model):
|
||||
_inherit = "account.invoice.line"
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
claim_line_id = vals.get('claim_line_id')
|
||||
if claim_line_id:
|
||||
del vals['claim_line_id']
|
||||
|
||||
line = super(AccountInvoiceLine, self).create(vals)
|
||||
if claim_line_id:
|
||||
claim_line = self.env['claim.line'].browse(claim_line_id)
|
||||
claim_line.refund_line_id = line.id
|
||||
|
||||
return line
|
||||
537
crm_claim_rma/models/crm_claim_rma.py
Normal file
537
crm_claim_rma/models/crm_claim_rma.py
Normal file
@@ -0,0 +1,537 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import math
|
||||
import calendar
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from openerp import models, fields, api, exceptions
|
||||
from openerp.tools.misc import (DEFAULT_SERVER_DATE_FORMAT,
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
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 SubstateSubstate(models.Model):
|
||||
""" To precise a state (state=refused; substates= reason 1, 2,...) """
|
||||
_name = "substate.substate"
|
||||
_description = "substate that precise a given state"
|
||||
|
||||
name = fields.Char(string='Sub state', required=True)
|
||||
substate_descr = fields.Text(
|
||||
string='Description',
|
||||
help="To give more information about the sub state")
|
||||
|
||||
|
||||
class ClaimLine(models.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
|
||||
@api.one
|
||||
def _line_total_amount(self):
|
||||
self.return_value = (self.unit_sale_price *
|
||||
self.product_returned_quantity)
|
||||
|
||||
@api.model
|
||||
def copy_data(self, default=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(ClaimLine, self).copy_data(default=std_default)
|
||||
|
||||
def get_warranty_return_partner(self):
|
||||
return self.env['product.supplierinfo'].get_warranty_return_partner()
|
||||
|
||||
name = fields.Char(string='Description', required=True, default=None)
|
||||
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')
|
||||
],
|
||||
string='Claim Subject', required=True,
|
||||
help="To describe the line product problem")
|
||||
claim_descr = fields.Text(
|
||||
string='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(
|
||||
string='Quantity', digits=(12, 2), help="Quantity of product returned")
|
||||
unit_sale_price = fields.Float(
|
||||
string='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.Float(
|
||||
string='Total return', compute='_line_total_amount',
|
||||
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')],
|
||||
string='Warranty type')
|
||||
guarantee_limit = fields.Date(
|
||||
string='Warranty limit',
|
||||
readonly=True,
|
||||
help="The warranty limit is computed as: invoice date + warranty "
|
||||
"defined on selected product.")
|
||||
warning = fields.Char(
|
||||
string='Warranty',
|
||||
readonly=True,
|
||||
help="If warranty has expired")
|
||||
warranty_type = fields.Selection(
|
||||
get_warranty_return_partner,
|
||||
string='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',
|
||||
default="draft")
|
||||
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')
|
||||
|
||||
@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, invoice, claim_type, product, claim_date):
|
||||
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':
|
||||
try:
|
||||
warranty_duration = product.seller_ids[0].warranty_duration
|
||||
except IndexError:
|
||||
raise ProductNoSupplier
|
||||
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):
|
||||
self.ensure_one()
|
||||
claim = self.claim_id
|
||||
try:
|
||||
values = self._warranty_limit_values(
|
||||
claim.invoice_id, claim.claim_type,
|
||||
self.product_id, claim.date)
|
||||
except InvoiceNoDate:
|
||||
raise exceptions.Warning(
|
||||
_('Error'), _('Cannot find any date for invoice. '
|
||||
'Must be a validated invoice.'))
|
||||
except ProductNoSupplier:
|
||||
raise exceptions.Warning(
|
||||
_('Error'), _('The product has no supplier configured.'))
|
||||
|
||||
self.write(values)
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def auto_set_warranty(self):
|
||||
""" Set warranty automatically
|
||||
if the user has not himself pressed on 'Calculate warranty state'
|
||||
button, it sets warranty for him"""
|
||||
for line in self:
|
||||
if not line.warning:
|
||||
line.set_warranty()
|
||||
return True
|
||||
|
||||
@api.returns('stock.location')
|
||||
def get_destination_location(self, product, warehouse):
|
||||
"""
|
||||
Compute and return the destination location to take
|
||||
for a return. Always take 'Supplier' one when return type different
|
||||
from company.
|
||||
"""
|
||||
location_dest_id = warehouse.lot_stock_id
|
||||
try:
|
||||
seller = product.seller_ids[0]
|
||||
if seller.warranty_return_partner != 'company':
|
||||
location_dest_id = seller.name.property_stock_supplier
|
||||
finally:
|
||||
return location_dest_id
|
||||
|
||||
@api.onchange('product_id', 'invoice_line_id')
|
||||
def _onchange_product_invoice_line(self):
|
||||
product = self.product_id
|
||||
invoice_line = self.invoice_line_id
|
||||
context = self.env.context
|
||||
|
||||
claim = context.get('claim_id')
|
||||
company_id = context.get('company_id')
|
||||
warehouse_id = context.get('warehouse_id')
|
||||
claim_type = context.get('claim_type')
|
||||
claim_date = context.get('claim_date')
|
||||
|
||||
# claim_exists = not isinstance(claim.id, NewId)
|
||||
if not claim 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 False
|
||||
if not (product and invoice_line):
|
||||
return False
|
||||
|
||||
invoice = invoice_line.invoice_id
|
||||
claim_line_model = self.env['claim.line']
|
||||
|
||||
if claim:
|
||||
claim = self.env['crm.claim'].browse(claim)
|
||||
company = claim.company_id
|
||||
warehouse = claim.warehouse_id
|
||||
claim_type = claim.claim_type
|
||||
claim_date = claim.date
|
||||
else:
|
||||
warehouse_obj = self.env['stock.warehouse']
|
||||
company_obj = self.env['res.company']
|
||||
company = company_obj.browse(company_id)
|
||||
warehouse = warehouse_obj.browse(warehouse_id)
|
||||
|
||||
values = {}
|
||||
try:
|
||||
warranty = claim_line_model._warranty_limit_values(
|
||||
invoice, claim_type, product, claim_date)
|
||||
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_model._warranty_return_address_values(
|
||||
product, company, warehouse)
|
||||
values.update(warranty_address)
|
||||
self.update(values)
|
||||
|
||||
def _warranty_return_address_values(self, product, company, warehouse):
|
||||
"""
|
||||
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 cases 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
|
||||
}
|
||||
sellers = product.seller_ids
|
||||
if sellers:
|
||||
seller = sellers[0]
|
||||
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 = self.get_destination_location(product, warehouse)
|
||||
return {
|
||||
'warranty_return_partner': return_address_id,
|
||||
'warranty_type': return_type,
|
||||
'location_dest_id': location_dest.id
|
||||
}
|
||||
|
||||
def set_warranty_return_address(self):
|
||||
self.ensure_one()
|
||||
claim = self.claim_id
|
||||
values = self._warranty_return_address_values(
|
||||
self.product_id, claim.company_id, claim.warehouse_id)
|
||||
self.write(values)
|
||||
return True
|
||||
|
||||
@api.one
|
||||
def set_warranty(self):
|
||||
""" Calculate warranty limit and address """
|
||||
if not (self.product_id and self.invoice_line_id):
|
||||
raise exceptions.Warning(
|
||||
_('Error'), _('Please set product and invoice.'))
|
||||
self.set_warranty_limit()
|
||||
self.set_warranty_return_address()
|
||||
|
||||
|
||||
# TODO add the option to split the claim_line in order to manage the same
|
||||
# product separately
|
||||
class CrmClaim(models.Model):
|
||||
_inherit = 'crm.claim'
|
||||
|
||||
def _get_default_warehouse(self):
|
||||
company_id = self.env.user.company_id.id
|
||||
wh_obj = self.env['stock.warehouse']
|
||||
wh = wh_obj.search([('company_id', '=', company_id)], limit=1)
|
||||
if not wh:
|
||||
raise exceptions.Warning(
|
||||
_('There is no warehouse for the current user\'s company.'))
|
||||
return wh
|
||||
|
||||
@api.one
|
||||
def name_get(self):
|
||||
return (self.id, '[{}] {}'.format(self.code or '', self.name))
|
||||
|
||||
@api.model
|
||||
def copy_data(self, default=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
std_default = {
|
||||
'invoice_ids': False,
|
||||
'picking_ids': False,
|
||||
'code': self.env['ir.sequence'].get('crm.claim'),
|
||||
}
|
||||
std_default.update(default)
|
||||
return super(CrmClaim, self).copy_data(std_default)
|
||||
|
||||
claim_type = fields.Selection(
|
||||
[('customer', 'Customer'),
|
||||
('supplier', 'Supplier'),
|
||||
('other', 'Other')],
|
||||
string='Claim type',
|
||||
required=True,
|
||||
default='customer',
|
||||
help="Customer: from customer to company.\n "
|
||||
"Supplier: from company to supplier.")
|
||||
claim_line_ids = fields.One2many('claim.line', 'claim_id',
|
||||
string='Claim lines')
|
||||
planned_revenue = fields.Float(string='Expected revenue')
|
||||
planned_cost = fields.Float(string='Expected cost')
|
||||
real_revenue = fields.Float(string='Real revenue')
|
||||
real_cost = fields.Float(string='Real cost')
|
||||
invoice_ids = fields.One2many('account.invoice', 'claim_id',
|
||||
string='Refunds')
|
||||
picking_ids = fields.One2many('stock.picking', 'claim_id', string='RMA')
|
||||
invoice_id = fields.Many2one(
|
||||
'account.invoice',
|
||||
string='Invoice',
|
||||
help='Related original Customer 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',
|
||||
default=_get_default_warehouse,
|
||||
required=True)
|
||||
|
||||
@api.onchange('invoice_id', 'warehouse_id', 'claim_type', 'date')
|
||||
def _onchange_invoice_warehouse_type_date(self):
|
||||
context = self.env.context
|
||||
claim_line_obj = self.env['claim.line']
|
||||
invoice_lines = self.invoice_id.invoice_line
|
||||
claim_lines = []
|
||||
if not self.warehouse_id:
|
||||
self.warehouse_id = self._get_default_warehouse()
|
||||
|
||||
claim_type = self.claim_type
|
||||
claim_date = self.date
|
||||
warehouse = self.warehouse_id
|
||||
company = self.company_id
|
||||
create_lines = context.get('create_lines')
|
||||
|
||||
def warranty_values(invoice, product):
|
||||
values = {}
|
||||
try:
|
||||
warranty = claim_line_obj._warranty_limit_values(
|
||||
invoice, claim_type, product, claim_date)
|
||||
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(
|
||||
product, company, warehouse)
|
||||
values.update(warranty_address)
|
||||
return values
|
||||
|
||||
if create_lines: # happens when the invoice is changed
|
||||
for invoice_line in invoice_lines:
|
||||
location_dest = claim_line_obj.get_destination_location(
|
||||
invoice_line.product_id, warehouse)
|
||||
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([0, 0, line])
|
||||
|
||||
for line in claim_lines:
|
||||
value = self._convert_to_cache(
|
||||
{'claim_line_ids': line}, validate=False)
|
||||
self.update(value)
|
||||
|
||||
if self.invoice_id:
|
||||
self.delivery_address_id = self.invoice_id.partner_id.id
|
||||
|
||||
@api.model
|
||||
def message_get_reply_to(self):
|
||||
""" 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.sudo()]
|
||||
|
||||
@api.model
|
||||
def message_get_suggested_recipients(self):
|
||||
recipients = super(CrmClaim, self).message_get_suggested_recipients()
|
||||
try:
|
||||
for claim in self:
|
||||
if claim.partner_id:
|
||||
self._message_add_suggested_recipient(
|
||||
recipients, claim,
|
||||
partner=claim.partner_id, reason=_('Customer'))
|
||||
elif claim.email_from:
|
||||
self._message_add_suggested_recipient(
|
||||
recipients, claim,
|
||||
email=claim.email_from, reason=_('Customer Email'))
|
||||
except exceptions.AccessError:
|
||||
# no read access rights -> just ignore suggested recipients
|
||||
# because this imply modifying followers
|
||||
pass
|
||||
return recipients
|
||||
56
crm_claim_rma/models/stock.py
Normal file
56
crm_claim_rma/models/stock.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp import models, fields, api
|
||||
|
||||
|
||||
class StockPicking(models.Model):
|
||||
_inherit = "stock.picking"
|
||||
|
||||
claim_id = fields.Many2one('crm.claim', string='Claim')
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if ('name' not in vals) or (vals.get('name') == '/'):
|
||||
vals['name'] = self.env['ir.sequence'].get(self._name)
|
||||
return super(StockPicking, self).create(vals)
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = "stock.move"
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
"""
|
||||
In case of a wrong picking out, We need to create a new stock_move in a
|
||||
picking already open.
|
||||
To avoid having to confirm the stock_move, we override the create and
|
||||
confirm it at the creation only for this case.
|
||||
"""
|
||||
move = super(StockMove, self).create(vals)
|
||||
if vals.get('picking_id'):
|
||||
picking = self.env['stock.picking'].browse(vals['picking_id'])
|
||||
if picking.claim_id and picking.picking_type_id.code == 'incoming':
|
||||
move.write({'state': 'confirmed'})
|
||||
return move
|
||||
@@ -25,13 +25,13 @@
|
||||
claim_type: customer
|
||||
partner_id: base.res_partner_3
|
||||
invoice_id: account_invoice_claim_refund
|
||||
state: open
|
||||
stage_id: crm_claim.stage_claim1
|
||||
-
|
||||
I prepare the wizard context.
|
||||
-
|
||||
!python {model: account.invoice.refund}: |
|
||||
claim_lines = self.pool.get('claim.line').search(cr, uid, [('claim_id','=',ref('claim_refund'))])
|
||||
context.update({'active_model': 'crm_claim', 'active_id': ref('claim_refund'), 'claim_id': ref('claim_refund'), 'claim_line_ids': [[4, claim_lines[0], False], [4, claim_lines[1], False]], 'invoice_ids':[ref('account_invoice_claim_refund')] })
|
||||
context.update({'active_model': 'crm_claim', 'active_id': ref('claim_refund'), 'claim_id': ref('claim_refund'), 'invoice_ids': [ref('account_invoice_claim_refund')] })
|
||||
-
|
||||
I create a refund wizard
|
||||
-
|
||||
@@ -18,4 +18,4 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from . import test_lp_1282584
|
||||
from . import test_picking_creation
|
||||
113
crm_claim_rma/tests/test_picking_creation.py
Normal file
113
crm_claim_rma/tests/test_picking_creation.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from openerp.tests import common
|
||||
|
||||
|
||||
class test_picking_creation(common.TransactionCase):
|
||||
""" Test the correct pickings are created by the wizard. """
|
||||
|
||||
def setUp(self):
|
||||
super(test_picking_creation, self).setUp()
|
||||
|
||||
self.WizardMakePicking = self.env['claim_make_picking.wizard']
|
||||
self.StockPicking = self.env['stock.picking']
|
||||
ClaimLine = self.env['claim.line']
|
||||
Claim = self.env['crm.claim']
|
||||
|
||||
self.product_id = self.env.ref('product.product_product_4')
|
||||
|
||||
self.partner_id = self.env.ref('base.res_partner_12')
|
||||
|
||||
self.customer_location_id = self.env.ref(
|
||||
'stock.stock_location_customers')
|
||||
|
||||
# Create the claim with a claim line
|
||||
self.claim_id = Claim.create(
|
||||
{
|
||||
'name': 'TEST CLAIM',
|
||||
'number': 'TEST CLAIM',
|
||||
'claim_type': 'customer',
|
||||
'delivery_address_id': self.partner_id.id,
|
||||
})
|
||||
|
||||
self.warehouse_id = self.claim_id.warehouse_id
|
||||
self.claim_line_id = ClaimLine.create(
|
||||
{
|
||||
'name': 'TEST CLAIM LINE',
|
||||
'claim_origine': 'none',
|
||||
'product_id': self.product_id.id,
|
||||
'claim_id': self.claim_id.id,
|
||||
'location_dest_id': self.warehouse_id.lot_stock_id.id,
|
||||
})
|
||||
|
||||
def test_00_new_product_return(self):
|
||||
"""Test wizard creates a correct picking for product return
|
||||
|
||||
"""
|
||||
wizard = self.WizardMakePicking.with_context({
|
||||
'active_id': self.claim_id.id,
|
||||
'partner_id': self.partner_id.id,
|
||||
'warehouse_id': self.warehouse_id.id,
|
||||
'picking_type': 'in',
|
||||
}).create({})
|
||||
wizard.action_create_picking()
|
||||
|
||||
self.assertEquals(len(self.claim_id.picking_ids), 1,
|
||||
"Incorrect number of pickings created")
|
||||
picking = self.claim_id.picking_ids[0]
|
||||
|
||||
self.assertEquals(picking.location_id, self.customer_location_id,
|
||||
"Incorrect source location")
|
||||
self.assertEquals(picking.location_dest_id,
|
||||
self.warehouse_id.lot_stock_id,
|
||||
"Incorrect destination location")
|
||||
|
||||
def test_01_new_delivery(self):
|
||||
"""Test wizard creates a correct picking for a new delivery
|
||||
|
||||
"""
|
||||
|
||||
WizardChangeProductQty = self.env['stock.change.product.qty']
|
||||
wizard_chg_qty = WizardChangeProductQty.with_context({
|
||||
'active_id': self.product_id.id,
|
||||
}).create({
|
||||
'product_id': self.product_id.id,
|
||||
'new_quantity': 12,
|
||||
})
|
||||
|
||||
wizard_chg_qty.change_product_qty()
|
||||
|
||||
wizard = self.WizardMakePicking.with_context({
|
||||
'active_id': self.claim_id.id,
|
||||
'partner_id': self.partner_id.id,
|
||||
'warehouse_id': self.warehouse_id.id,
|
||||
'picking_type': 'out',
|
||||
}).create({})
|
||||
wizard.action_create_picking()
|
||||
|
||||
self.assertEquals(len(self.claim_id.picking_ids), 1,
|
||||
"Incorrect number of pickings created")
|
||||
picking = self.claim_id.picking_ids[0]
|
||||
|
||||
self.assertEquals(picking.location_id, self.warehouse_id.lot_stock_id,
|
||||
"Incorrect source location")
|
||||
self.assertEquals(picking.location_dest_id, self.customer_location_id,
|
||||
"Incorrect destination location")
|
||||
392
crm_claim_rma/views/crm_claim_rma.xml
Normal file
392
crm_claim_rma/views/crm_claim_rma.xml
Normal file
@@ -0,0 +1,392 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- Claim line views -->
|
||||
<!-- SEARCH -->
|
||||
<record id="view_crm_claim_lines_filter" model="ir.ui.view">
|
||||
<field name="name">CRM - Claims Search</field>
|
||||
<field name="model">claim.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Claims">
|
||||
<filter icon="terp-check" string="Current" name="current"
|
||||
domain="[('state','in',('draft', 'refused', 'treated'))]"
|
||||
separator="1" help="Draft and Open Claims" />
|
||||
<filter icon="terp-camera_test"
|
||||
string="In Progress"
|
||||
domain="[('state','in',('confirmed','in_to_control','in_to_treate'))]"
|
||||
separator="1" help="In Progress Claims"/>
|
||||
<separator orientation="vertical"/>
|
||||
<field name="state" select='1'/>
|
||||
<field name="substate_id" select='1'/>
|
||||
<field name="name" select='1'/>
|
||||
<field name="warning" select='1'/>
|
||||
<field name="invoice_line_id" select='1'/>
|
||||
<field name="product_id" select='1'/>
|
||||
<field name="prodlot_id" select='1'/>
|
||||
<newline/>
|
||||
<group expand="0" string="More">
|
||||
<field name="last_state_change" select='1'/>
|
||||
<field name="guarantee_limit" select='1'/>
|
||||
<field name="return_value" select='1'/>
|
||||
<field name="name" select='1'/>
|
||||
</group>
|
||||
<newline/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Invoice" icon="terp-dolar"
|
||||
domain="[]" help="Invoice"
|
||||
context="{'group_by':'invoice_id'}"/>
|
||||
<filter string="Product" icon="terp-product"
|
||||
domain="[]" help="Product"
|
||||
context="{'group_by':'product_id'}"/>
|
||||
<separator orientation="vertical"/>
|
||||
<filter string="Substate" icon="terp-stage"
|
||||
domain="[]" context="{'group_by':'substate_id'}"/>
|
||||
<filter string="Claim n°" icon="terp-emblem-documents"
|
||||
domain="[]" context="{'group_by':'claim_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- TREE -->
|
||||
<record model="ir.ui.view" id="crm_claim_line_tree_view">
|
||||
<field name="name">CRM - Claims Tree</field>
|
||||
<field name="model">claim.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Claim lines">
|
||||
<field name="claim_id" invisible="1"/>
|
||||
<field name="state"/>
|
||||
<field name="substate_id"/>
|
||||
<field name="product_id"/>
|
||||
<field name="name"/>
|
||||
<field name="prodlot_id"/>
|
||||
<field name="warning"/>
|
||||
<field name="warranty_type"/>
|
||||
<field name="warranty_return_partner"/>
|
||||
<button name="set_warranty" string="Compute Waranty" type="object" icon="gtk-justify-fill"/>
|
||||
<field name="product_returned_quantity"/>
|
||||
<field name="claim_origine"/>
|
||||
<field name="refund_line_id"/>
|
||||
<field name="move_in_id"/>
|
||||
<field name="move_out_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- FORM -->
|
||||
<record model="ir.ui.view" id="crm_claim_line_form_view">
|
||||
<field name="name">CRM - Claim product return line Form</field>
|
||||
<field name="model">claim.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Claim Line" version="7.0">
|
||||
<header>
|
||||
<button name="set_warranty" string="Calculate warranty state" type="object" class="oe_highlight"/>
|
||||
</header>
|
||||
<sheet string="Claims">
|
||||
<div class="oe_title">
|
||||
<group>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
</group>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Returned good">
|
||||
<field name="product_returned_quantity"/>
|
||||
<field name="product_id" />
|
||||
<field name="prodlot_id"/>
|
||||
<field name="unit_sale_price"/>
|
||||
<field name="return_value"/>
|
||||
</group>
|
||||
<group string="Linked Document">
|
||||
<field name="claim_id"/>
|
||||
<field name="invoice_line_id"/>
|
||||
<field name="refund_line_id"/>
|
||||
<field name="move_in_id"/>
|
||||
<field name="move_out_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Problem">
|
||||
<field name="claim_origine" nolabel="1" colspan="4"/>
|
||||
<field name="claim_descr" nolabel="1" colspan="4"/>
|
||||
</group>
|
||||
<group string="Warranty">
|
||||
<field name="guarantee_limit"/>
|
||||
<field name="warning"/>
|
||||
<field name="warranty_return_partner"/>
|
||||
<field name="warranty_type"/>
|
||||
</group>
|
||||
</group>
|
||||
<separator string="State" colspan="4"/>
|
||||
<group col="6" colspan="4">
|
||||
<field name="state"/>
|
||||
<field name="substate_id" widget='selection'/>
|
||||
<field name="last_state_change"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
A second slightly modified form view to be used for the claim_line_ids
|
||||
field in the crm.claim form view. Defining it here instead of directly
|
||||
inside the field allows us to write only the changes and reference it
|
||||
-->
|
||||
<record id="crm_claim_line_view_form_embedded" model="ir.ui.view">
|
||||
<field name="name">Claim line form view to be used inside claim tree</field>
|
||||
<field name="mode">primary</field>
|
||||
<field name="model">claim.line</field>
|
||||
<field name="priority" eval="30"/>
|
||||
<field name="inherit_id" ref="crm_claim_line_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="claim_id" position="attributes">
|
||||
<attribute name="readonly">1</attribute>
|
||||
</field>
|
||||
<field name="product_id" position="attributes">
|
||||
<attribute name="context">{'claim_id': parent.id, 'company_id': parent.company_id, 'warehouse_id': parent.warehouse_id, 'claim_type': parent.claim_type, 'claim_date': parent.date}</attribute>
|
||||
</field>
|
||||
<field name="invoice_line_id" position="attributes">
|
||||
<attribute name="context">{'claim_id': parent.id, 'company_id': parent.company_id, 'warehouse_id': parent.warehouse_id, 'claim_type': parent.claim_type, 'claim_date': parent.date}</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- CLAIM VIEWS -->
|
||||
<record model="ir.ui.view" id="crm_case_claims_tree_view">
|
||||
<field name="name">CRM - Claims Tree</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="inherit_id" ref="crm_claim.crm_case_claims_tree_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="stage_id" position="after">
|
||||
<field name="section_id"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Crm claim Search view -->
|
||||
<record id="view_crm_case_claims_filter" model="ir.ui.view">
|
||||
<field name="name">CRM - Claims Search</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="inherit_id" ref="crm_claim.view_crm_case_claims_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}" position="before">
|
||||
<filter string="Sales Team" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'section_id'}"/>
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Right side link to orders -->
|
||||
<act_window
|
||||
id="act_crm_claim_rma_sale_orders"
|
||||
name="Quotations and Sales"
|
||||
res_model="sale.order"
|
||||
src_model="crm.claim"/>
|
||||
<!-- Right side link to invoices -->
|
||||
<act_window
|
||||
domain="[('type', '=', 'out_invoice')]"
|
||||
id="act_crm_claim_rma_invoice_out"
|
||||
name="Customer Invoices"
|
||||
res_model="account.invoice"
|
||||
src_model="crm.claim"/>
|
||||
<!-- Right side link to invoices -->
|
||||
<act_window
|
||||
domain="[('type', '=', 'in_invoice')]"
|
||||
id="act_crm_claim_rma_invoice_in"
|
||||
name="Supplier Invoices"
|
||||
res_model="account.invoice"
|
||||
src_model="crm.claim"/>
|
||||
<!-- Right side link to refunds -->
|
||||
<act_window
|
||||
domain="[('type', '=', 'out_refund')]"
|
||||
id="act_crm_claim_rma_refunds_out"
|
||||
name="Customer Refunds"
|
||||
res_model="account.invoice"
|
||||
src_model="crm.claim"/>
|
||||
<!-- Right side link to refunds -->
|
||||
<act_window
|
||||
domain="[('type', '=', 'in_refund')]"
|
||||
id="act_crm_claim_rma_refunds_in"
|
||||
name="Supplier Refunds"
|
||||
res_model="account.invoice"
|
||||
src_model="crm.claim"/>
|
||||
<!-- Right side link to picking in -->
|
||||
<act_window
|
||||
domain="[('picking_type_id.code', '=', 'incoming')]"
|
||||
id="act_crm_claim_rma_picking_in"
|
||||
name="Incoming Shipment and Returns"
|
||||
res_model="stock.picking"
|
||||
src_model="crm.claim"/>
|
||||
<!-- Right side link to picking out -->
|
||||
<act_window
|
||||
domain="[('picking_type_id.code', '=', 'outgoing')]"
|
||||
id="act_crm_claim_rma_picking_out"
|
||||
name="Deliveries"
|
||||
res_model="stock.picking"
|
||||
src_model="crm.claim"/>
|
||||
|
||||
<record model="ir.actions.act_window" id="crm_claim.crm_case_categ_claim0">
|
||||
<field name="context">{"search_default_user_id":uid, "stage_type":'claim'}</field>
|
||||
</record>
|
||||
|
||||
<!-- Claim lines action -->
|
||||
<record model="ir.actions.act_window" id="act_crm_case_claim_lines">
|
||||
<field name="name">Claim lines</field>
|
||||
<field name="res_model">claim.line</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="crm_claim_line_tree_view"/>
|
||||
<field name="search_view_id" ref="view_crm_claim_lines_filter"/>
|
||||
</record>
|
||||
|
||||
<!-- substates action -->
|
||||
<record id="act_crm_claim_substates" model="ir.actions.act_window">
|
||||
<field name="name">Claim line substates</field>
|
||||
<field name="res_model">substate.substate</field>
|
||||
<field name="view_type">form</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu -->
|
||||
<menuitem
|
||||
name="Claim lines"
|
||||
id="menu_crm_case_claims_claim_lines"
|
||||
parent="base.menu_aftersale"
|
||||
action="act_crm_case_claim_lines"
|
||||
sequence="2"/>
|
||||
<menuitem
|
||||
name="Claim line substates"
|
||||
id="menu_crm_case_claims_claim_line_substates"
|
||||
parent="crm_claim.menu_config_claim"
|
||||
action="act_crm_claim_substates"
|
||||
sequence="2"/>
|
||||
|
||||
<record model="ir.ui.view" id="crm_claim_rma_form_view">
|
||||
<field name="name">CRM - Claim product return Form</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="inherit_id" ref="crm_claim.crm_case_claims_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- Header/workflow Buttons -->
|
||||
<field name="stage_id" position="before">
|
||||
<button
|
||||
name="%(action_claim_picking_in)d"
|
||||
type="action" string="New Products Return"
|
||||
attrs="{'invisible': [('partner_id','=', False)]}"
|
||||
context="{'warehouse_id': warehouse_id, 'partner_id': partner_id}"/>
|
||||
<button
|
||||
name="%(action_claim_picking_out)d"
|
||||
type="action"
|
||||
string="New Delivery"
|
||||
attrs="{'invisible': [('partner_id','=', False)]}"
|
||||
context="{'warehouse_id': warehouse_id, 'partner_id': partner_id}"/>
|
||||
<button
|
||||
name="%(account.action_account_invoice_refund)d"
|
||||
type='action'
|
||||
string='New Refund'
|
||||
attrs="{'invisible': [('invoice_id','=', False)]}"
|
||||
context="{'invoice_ids': [invoice_id], 'claim_line_ids': claim_line_ids, 'description': name, 'claim_id': id}"/>
|
||||
</field>
|
||||
<field name="date_deadline" position="after">
|
||||
<field name="claim_type" context="{'create_lines': False}"/>
|
||||
<field name="warehouse_id" context="{'create_lines': False}"/>
|
||||
</field>
|
||||
<field name="date" position="replace"/>
|
||||
<field name="user_id" position="before">
|
||||
<field name="date" context="{'create_lines': False}"/>
|
||||
</field>
|
||||
<!-- Smart buttons -->
|
||||
<xpath expr="//sheet[@string='Claims']/group[1]" position="after">
|
||||
<div class="oe_right oe_button_box" style="width: 300px;" name="buttons">
|
||||
<button name="%(act_crm_claim_rma_sale_orders)d" type="action"
|
||||
string="Quotation/Sales"
|
||||
icon="fa-strikethrough"
|
||||
class="oe_stat_button"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
|
||||
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"/>
|
||||
|
||||
<button name="%(act_crm_claim_rma_invoice_out)d" type="action"
|
||||
string="Invoices"
|
||||
icon="fa-pencil-square-o"
|
||||
class="oe_stat_button"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
|
||||
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"/>
|
||||
|
||||
<button name="%(act_crm_claim_rma_refunds_out)d" type="action"
|
||||
string="Refunds"
|
||||
icon="fa-file-text-o"
|
||||
class="oe_stat_button"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
|
||||
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"/>
|
||||
|
||||
<button name="%(act_crm_claim_rma_invoice_in)d" type="action"
|
||||
string="Invoices"
|
||||
icon="fa-pencil-square-o"
|
||||
class="oe_stat_button"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['customer','other'])]}"
|
||||
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"/>
|
||||
|
||||
<button name="%(act_crm_claim_rma_refunds_in)d" type="action"
|
||||
string="Refunds"
|
||||
icon="fa-file-text-o"
|
||||
class="oe_stat_button"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['customer','other'])]}"
|
||||
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"/>
|
||||
|
||||
<button name="%(act_crm_claim_rma_picking_in)d" type="action"
|
||||
string="Products"
|
||||
icon="fa-reply"
|
||||
class="oe_stat_button"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
|
||||
context="{'search_default_claim_id': active_id,'search_default_user_id':False}"/>
|
||||
|
||||
<button name="%(act_crm_claim_rma_picking_out)d" type="action"
|
||||
string="Deliveries"
|
||||
icon="fa-truck"
|
||||
class="oe_stat_button"
|
||||
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
|
||||
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//sheet[@string='Claims']/group[1]" position="replace">
|
||||
<div class="oe_title oe_left">
|
||||
<h1><field name="code"/></h1>
|
||||
<div class="oe_edit_only">
|
||||
<label for="name"/>
|
||||
</div>
|
||||
<h1><small><field name="name"/></small></h1>
|
||||
</div>
|
||||
</xpath>
|
||||
<!-- Remove domain and widget attributes -->
|
||||
<field name="categ_id" widget="selection" position="replace">
|
||||
<field name="categ_id"/>
|
||||
</field>
|
||||
<!-- New tabs for products return and generated documents -->
|
||||
<page string="Follow Up" position="before">
|
||||
<page string="Product Return">
|
||||
<group name="Product Return">
|
||||
<separator string="Product Return" colspan="4"/>
|
||||
<group>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="invoice_id" domain="['|',('commercial_partner_id','=',partner_id),('partner_id','=',partner_id)]" context="{'create_lines': True}"/>
|
||||
<field name="delivery_address_id" context="{'tree_view_ref': 'crm_claim_rma.view_partner_contact_tree', 'search_default_parent_id': partner_id}"/>
|
||||
</group>
|
||||
<group>
|
||||
<!-- Place for mass return button from crm_rma_lot_mass_return -->
|
||||
</group>
|
||||
<field
|
||||
name="claim_line_ids"
|
||||
nolabel="1"
|
||||
context="{'default_claim_id': active_id, 'form_view_ref': 'crm_claim_rma.crm_claim_line_view_form_embedded'}"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Generated Documents">
|
||||
<separator colspan="2" string="Refunds"/>
|
||||
<field name="invoice_ids" colspan="4" readonly="1"/>
|
||||
<separator colspan="2" string="Receptions / Deliveries"/>
|
||||
<field name="picking_ids" colspan="4" readonly="1"/>
|
||||
</page>
|
||||
</page>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -15,7 +15,6 @@
|
||||
<field name="email"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
<field name="is_company" invisible="1"/>
|
||||
<field name="country" invisible="1"/>
|
||||
<field name="country_id"/>
|
||||
<field name="parent_id" invisible="1"/>
|
||||
</tree>
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from . import claim_make_picking
|
||||
from . import account_invoice_refund
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
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)
|
||||
237
crm_claim_rma/wizards/claim_make_picking.py
Normal file
237
crm_claim_rma/wizards/claim_make_picking.py
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
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',
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
crm_claim_rma for OpenERP
|
||||
Copyright (c) 2015 Eezee-It (www.eezee-it.com)
|
||||
Copyright (C) 2011 Akretion Benoît GUILLOT <benoit.guillot@akretion.com>
|
||||
The licence is in the file __openerp__.py
|
||||
-->
|
||||
@@ -12,15 +13,19 @@
|
||||
<field name="model">claim_make_picking.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Select exchange lines to add in picking" version="7.0">
|
||||
<separator string="Locations" colspan="4"/>
|
||||
<field name="claim_line_source_location" nolabel="1" />
|
||||
<field name="claim_line_dest_location" nolabel="1" />
|
||||
<separator string="Select lines for picking" colspan="4"/>
|
||||
<field name="claim_line_ids" nolabel="1" colspan="4"/>
|
||||
<group name="locations" string="Locations">
|
||||
<field name="claim_line_source_location_id"/>
|
||||
<field name="claim_line_dest_location_id"/>
|
||||
</group>
|
||||
<separator string="Select lines for picking"/>
|
||||
<field name="claim_line_ids" nolabel="1"/>
|
||||
<footer>
|
||||
<button name="action_create_picking" string="Create picking" type="object" class="oe_highlight"/>
|
||||
<button
|
||||
string="Create picking"
|
||||
name="action_create_picking" type="object"
|
||||
class="oe_highlight"/>
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
<button string="Cancel" class="oe_link" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
Reference in New Issue
Block a user