Merge pull request #4 from LeartS/port_crm_claim_rma_fixes

Improvements to the port of crm_claim_rma
This commit is contained in:
Aristobulo Meneses
2015-08-07 15:22:31 +02:00
19 changed files with 902 additions and 981 deletions

View File

@@ -22,7 +22,5 @@
#
##############################################################################
from . import wizard
from . import crm_claim_rma
from . import account_invoice
from . import stock
from . import models
from . import wizards

View File

@@ -71,30 +71,33 @@ Contributors:
* Javier Carrasco <javier.carrasco@eezee-it.com>
""",
'author': "Akretion, Camptocamp, Eezee-it, "
'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.eezee-it.com, http://www.wearemonk.com',
'license': 'AGPL-3',
'depends': ['sale',
'stock',
'crm_claim',
'crm_claim_code',
'product_warranty',
],
'data': ['wizard/claim_make_picking_view.xml',
'crm_claim_rma_view.xml',
'security/ir.model.access.csv',
'account_invoice_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,
}

View File

@@ -1,416 +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" />
<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>
<!-- 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>
<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" 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" 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" context="{'claim_id': parent.id, 'company_id': parent.company_id, 'warehouse_id': parent.warehouse_id, 'claim_type': parent.claim_type, 'claim_date': parent.date}"/>
<field name="prodlot_id"/>
<field name="unit_sale_price"/>
<field name="return_value"/>
</group>
<group string="Linked Document">
<field name="invoice_line_id" context="{'claim_id': parent.id, 'company_id': parent.company_id, 'warehouse_id': parent.warehouse_id, 'claim_type': parent.claim_type, 'claim_date': parent.date}"/>
<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':[('stage_id', 'not in', [%(crm_claim.stage_claim1)d, %(crm_claim.stage_claim5)d])]}">
<separator string="Actions" colspan="4"/>
<button name="%(action_claim_picking_in)d"
string="New Products Return"
type="action" target="new"
context="{'warehouse_id': warehouse_id,
'partner_id': partner_id}"
class="oe_highlight"/>
<button name="%(action_claim_picking_out)d"
string="New Delivery"
type="action" target="new"
context="{'warehouse_id': warehouse_id,
'partner_id': partner_id}"
class="oe_highlight"/>
<button name="%(account.action_account_invoice_refund)d"
type='action' string='New Refund'
context="{
'invoice_ids': [invoice_id],
'claim_line_ids': claim_line_ids,
'description': name,
'claim_id': id}"
class="oe_highlight"/>
</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="[('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.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" context="{'create_lines': False}" />
<field name="warehouse_id" context="{'create_lines': False}"/>
</field>
<field name="date" position="replace">
</field>
<field name="user_id" position="before">
<field name="name" />
<field name="date" context="{'create_lines': False}"/>
</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="Sales"
icon="fa-strikethrough"
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"
class="oe_inline oe_stat_button"/>
<button name="%(act_crm_claim_rma_invoice_out)d" type="action"
string="Invoices"
icon="fa-pencil-square-o"
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"
class="oe_inline oe_stat_button"/>
<button name="%(act_crm_claim_rma_refunds_out)d" type="action"
string="Refunds"
icon="fa-file-text-o"
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"
class="oe_inline oe_stat_button"/>
<button name="%(act_crm_claim_rma_invoice_in)d" type="action"
string="Invoices"
icon="fa-pencil-square-o"
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['customer','other'])]}"
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"
class="oe_inline oe_stat_button"/>
<button name="%(act_crm_claim_rma_refunds_in)d" type="action"
string="Refunds"
icon="fa-file-text-o"
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['customer','other'])]}"
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"
class="oe_inline oe_stat_button"/>
<button name="%(act_crm_claim_rma_picking_in)d" type="action"
string="Products"
icon="fa-reply"
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
context="{'search_default_claim_id': active_id,'search_default_user_id':False}"
class="oe_inline oe_stat_button"/>
<button name="%(act_crm_claim_rma_picking_out)d" type="action"
string="Deliveries"
icon="fa-truck"
attrs="{'invisible': ['|',('partner_id','=', False),('claim_type','in', ['supplier','other'])]}"
context="{'search_default_partner_id': [partner_id],'search_default_user_id':False}"
class="oe_inline oe_stat_button"/>
</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">
<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>

View File

@@ -0,0 +1,3 @@
from . import crm_claim_rma
from . import account_invoice
from . import stock

View File

@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright 2015 Eezee-It
# 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
@@ -22,33 +22,35 @@
#
##############################################################################
from openerp.models import Model, api, _
from openerp import fields
from openerp import models, fields, api, exceptions
from openerp.tools.translate import _
class AccountInvoice(Model):
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."""
new_lines = []
inv_line_obj = self.env['account.invoice.line']
claim_line_obj = self.env['claim.line']
"""
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)
for __, claim_line_id, __ in self.env.context.get('claim_line_ids'):
line = claim_line_obj.browse(claim_line_id)
if not line.refund_line_id:
# 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 = inv_line_obj.browse(line.invoice_line_id.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
@@ -57,17 +59,15 @@ class AccountInvoice(Model):
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]
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 Warning(
raise exceptions.Warning(
_('A refund has already been created for this claim !'))
return [(0, 0, l) for l in new_lines]
@@ -79,24 +79,23 @@ class AccountInvoice(Model):
journal_id=journal_id)
if self.env.context.get('claim_id'):
result['claim_id'] = self.env.context.get('claim_id')
result['claim_id'] = self.env.context['claim_id']
return result
class AccountInvoiceLine(Model):
class AccountInvoiceLine(models.Model):
_inherit = "account.invoice.line"
@api.model
def create(self, vals):
claim_line_id = False
if vals.get('claim_line_id'):
claim_line_id = vals['claim_line_id']
claim_line_id = vals.get('claim_line_id')
if claim_line_id:
del vals['claim_line_id']
line_id = super(AccountInvoiceLine, self).create(vals)
line = super(AccountInvoiceLine, self).create(vals)
if claim_line_id:
claim_line = self.env['claim.line'].browse(claim_line_id)
claim_line.write({'refund_line_id': line_id.id})
claim_line.refund_line_id = line.id
return line_id
return line

View File

@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright 2015 Eezee-It
# 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
@@ -21,18 +21,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.models import Model, api, _
from openerp import fields
from openerp.tools import (DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT)
from openerp.exceptions import except_orm, Warning
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
@@ -44,7 +42,7 @@ class ProductNoSupplier(Exception):
because the product has no supplier. """
class SubstateSubstate(Model):
class SubstateSubstate(models.Model):
""" To precise a state (state=refused; substates= reason 1, 2,...) """
_name = "substate.substate"
_description = "substate that precise a given state"
@@ -55,7 +53,7 @@ class SubstateSubstate(Model):
help="To give more information about the sub state")
class ClaimLine(Model):
class ClaimLine(models.Model):
"""
Class to handle a product return line (corresponding to one invoice line)
"""
@@ -88,9 +86,7 @@ class ClaimLine(Model):
return super(ClaimLine, self).copy_data(default=std_default)
def get_warranty_return_partner(self):
seller = self.env['product.supplierinfo']
result = seller.get_warranty_return_partner()
return result
return self.env['product.supplierinfo'].get_warranty_return_partner()
name = fields.Char(string='Description', required=True, default=None)
claim_origine = fields.Selection(
@@ -203,7 +199,6 @@ class ClaimLine(Model):
``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)
@@ -229,11 +224,10 @@ class ClaimLine(Model):
date_invoice = datetime.strptime(date_invoice,
DEFAULT_SERVER_DATE_FORMAT)
if claim_type == 'supplier':
suppliers = product.seller_ids
if not suppliers:
try:
warranty_duration = product.seller_ids[0].warranty_duration
except IndexError:
raise ProductNoSupplier
supplier = suppliers[0]
warranty_duration = supplier.warranty_duration
else:
warranty_duration = product.warranty
@@ -250,20 +244,17 @@ class ClaimLine(Model):
'warning': warning}
def set_warranty_limit(self):
self.ensure_one()
claim = self.claim_id
invoice = claim.invoice_id
claim_type = claim.claim_type
claim_date = claim.date
product = self.product_id
try:
values = self._warranty_limit_values(invoice, claim_type, product,
claim_date)
values = self._warranty_limit_values(
claim.invoice_id, claim.claim_type, self.product_id, claim.date)
except InvoiceNoDate:
raise Warning(
raise exceptions.Warning(
_('Error'), _('Cannot find any date for invoice. '
'Must be a validated invoice.'))
except ProductNoSupplier:
raise Warning(
raise exceptions.Warning(
_('Error'), _('The product has no supplier configured.'))
self.write(values)
@@ -279,19 +270,20 @@ class ClaimLine(Model):
line.set_warranty()
return True
@api.returns('stock.location')
def get_destination_location(self, product, warehouse):
"""Compute and return the destination location ID to take
"""
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.id
if product:
sellers = product.seller_ids
if sellers:
seller = sellers[0]
return_type = seller.warranty_return_partner
if return_type != 'company':
location_dest_id = seller.name.property_stock_supplier.id
return location_dest_id
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):
@@ -315,7 +307,7 @@ class ClaimLine(Model):
return False
invoice = invoice_line.invoice_id
claim_line_obj = self.env['claim.line']
claim_line_model = self.env['claim.line']
if claim:
claim = self.env['crm.claim'].browse(claim)
@@ -331,7 +323,7 @@ class ClaimLine(Model):
values = {}
try:
warranty = claim_line_obj._warranty_limit_values(
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
@@ -339,27 +331,27 @@ class ClaimLine(Model):
values.update({'guarantee_limit': False, 'warning': False})
else:
values.update(warranty)
warranty_address = claim_line_obj._warranty_return_address_values(
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
"""
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:
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}
return {
'warranty_return_partner': False,
'warranty_type': False,
'location_dest_id': False
}
sellers = product.seller_ids
if sellers:
seller = sellers[0]
@@ -371,35 +363,34 @@ class ClaimLine(Model):
company.partner_id)
return_address_id = return_address.id
return_type = 'company'
location_dest_id = self.get_destination_location(product, warehouse)
return {'warranty_return_partner': return_address_id,
'warranty_type': return_type,
'location_dest_id': location_dest_id}
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
product = self.product_id
company = claim.company_id
warehouse = claim.warehouse_id
values = self._warranty_return_address_values(
product, company, warehouse)
self.product_id, claim.company_id, claim.warehouse_id)
self.write(values)
return True
@api.multi
@api.one
def set_warranty(self):
""" Calculate warranty limit and address """
for claim_line in self:
if not (claim_line.product_id and claim_line.invoice_line_id):
raise Warning(
_('Error'), _('Please set product and invoice.'))
claim_line.set_warranty_limit()
claim_line.set_warranty_return_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(Model):
class CrmClaim(models.Model):
_inherit = 'crm.claim'
def _get_default_warehouse(self):
@@ -407,19 +398,16 @@ class CrmClaim(Model):
wh_obj = self.env['stock.warehouse']
wh = wh_obj.search([('company_id', '=', company_id)], limit=1)
if not wh:
raise Warning(
raise exceptions.Warning(
_('There is no warehouse for the current user\'s company.'))
return wh
@api.multi
@api.one
def name_get(self):
res = []
for claim in self:
code = claim.code and str(claim.code) or ''
res.append((claim.id, '[' + code + '] ' + claim.name))
return res
return (self.id, '[{}] {}'.format(self.code or '', self.name))
def copy_data(self, cr, uid, id, default=None, context=None):
@api.model
def copy_data(self, default=None):
if default is None:
default = {}
std_default = {
@@ -428,8 +416,7 @@ class CrmClaim(Model):
'code': self.env['ir.sequence'].get('crm.claim'),
}
std_default.update(default)
return super(CrmClaim, self).copy_data(cr, uid, id, std_default,
context=context)
return super(CrmClaim, self).copy_data(std_default)
claim_type = fields.Selection(
[('customer', 'Customer'),
@@ -441,7 +428,7 @@ class CrmClaim(Model):
help="Customer: from customer to company.\n "
"Supplier: from company to supplier.")
claim_line_ids = fields.One2many('claim.line', 'claim_id',
string='Return lines')
string='Claim lines')
planned_revenue = fields.Float(string='Expected revenue')
planned_cost = fields.Float(string='Expected cost')
real_revenue = fields.Float(string='Real revenue')
@@ -498,7 +485,7 @@ class CrmClaim(Model):
if create_lines: # happens when the invoice is changed
for invoice_line in invoice_lines:
location_dest_id = claim_line_obj.get_destination_location(
location_dest = claim_line_obj.get_destination_location(
invoice_line.product_id, warehouse)
line = {
'name': invoice_line.name,
@@ -507,7 +494,7 @@ class CrmClaim(Model):
'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,
'location_dest_id': location_dest.id,
'state': 'draft',
}
line.update(warranty_values(invoice_line.invoice_id,
@@ -531,8 +518,7 @@ class CrmClaim(Model):
@api.model
def message_get_suggested_recipients(self):
recipients = super(CrmClaim, self
).message_get_suggested_recipients()
recipients = super(CrmClaim, self).message_get_suggested_recipients()
try:
for claim in self:
if claim.partner_id:
@@ -543,7 +529,7 @@ class CrmClaim(Model):
self._message_add_suggested_recipient(
recipients, claim,
email=claim.email_from, reason=_('Customer Email'))
except except_orm:
except exceptions.AccessError:
# no read access rights -> just ignore suggested recipients
# because this imply modifying followers
pass

View File

@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright 2015 Eezee-It
# 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
@@ -22,40 +22,35 @@
#
##############################################################################
from openerp.models import Model, api
from openerp.fields import Many2one
from openerp import models, fields, api
class StockPicking(Model):
class StockPicking(models.Model):
_inherit = "stock.picking"
claim_id = Many2one('crm.claim', string='Claim')
claim_id = fields.Many2one('crm.claim', string='Claim')
@api.model
def create(self, vals):
if ('name' not in vals) or (vals.get('name') == '/'):
sequence_obj = self.env['ir.sequence']
seq_obj_name = self._name
vals['name'] = sequence_obj.get(seq_obj_name)
picking = super(StockPicking, self).create(vals)
return picking
vals['name'] = self.env['ir.sequence'].get(self._name)
return super(StockPicking, self).create(vals)
# 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 StockMove(Model):
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_obj = self.env['stock.picking']
picking = picking_obj.browse(vals['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

View File

@@ -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

View File

@@ -1,101 +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.common import TransactionCase
class test_lp_1282584(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()
self.wizard_make_picking_obj = self.env['claim_make_picking.wizard']
line_obj = self.env['claim.line']
claim_obj = self.env['crm.claim']
self.product = self.env.ref('product.product_product_4')
self.partner = self.env.ref('base.res_partner_12')
# Create the claim with a claim line
self.claim = claim_obj.create(
{
'name': 'TEST CLAIM',
'code': 'TEST CLAIM',
'claim_type': 'customer',
'delivery_address_id': self.partner.id,
}
)
self.warehouse = self.claim.warehouse_id
self.claim_line = line_obj.create(
{
'name': 'TEST CLAIM LINE',
'claim_origine': 'none',
'product_id': self.product.id,
'claim_id': self.claim.id,
'location_dest_id': self.warehouse.lot_stock_id.id
}
)
def test_00(self):
""" Test wizard opened view model for a new product return """
context = {
'active_id': self.claim.id,
'partner_id': self.partner.id,
'warehouse_id': self.warehouse.id,
'picking_type': 'in',
}
wizard = self.wizard_make_picking_obj.with_context(context).create({})
res = wizard.action_create_picking()
self.assertEquals(res.get('res_model'), 'stock.picking',
"Wrong model defined")
def test_01(self):
""" Test wizard opened view model for a new delivery """
wizard_change_product_qty_obj = self.env['stock.change.product.qty']
context = {'active_id': self.product.id}
wizard_change_product_qty = wizard_change_product_qty_obj.create({
'product_id': self.product.id,
'new_quantity': 12
})
wizard_change_product_qty.with_context(context).change_product_qty()
context = {
'active_id': self.claim.id,
'partner_id': self.partner.id,
'warehouse_id': self.warehouse.id,
'picking_type': 'out',
}
wizard = self.wizard_make_picking_obj.with_context(context).create({})
res = wizard.action_create_picking()
self.assertEquals(res.get('res_model'), 'stock.picking',
"Wrong model defined")

View 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")

View 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>
<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>
<!-- 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"/>
</data>
</openerp>

View File

@@ -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>

View File

@@ -1,279 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright 2015 Eezee-It
# 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.models import api, TransientModel, _
from openerp.fields import Many2many, Many2one
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.exceptions import Warning
from openerp import workflow
from openerp import workflow
import time
class ClaimMakePicking(TransientModel):
_name = 'claim_make_picking.wizard'
_description = 'Wizard to create pickings from claim lines'
# Get default source location
def _get_source_loc(self):
loc_id = False
context = self.env.context
if context is None:
context = {}
warehouse_obj = self.env['stock.warehouse']
warehouse_id = context.get('warehouse_id')
picking_type = context.get('picking_type')
partner_id = context.get('partner_id')
if picking_type == 'out':
loc_id = warehouse_obj.browse(
warehouse_id).lot_stock_id.id
elif partner_id:
loc_id = self.env['res.partner'].browse(
partner_id).property_stock_customer.id
return loc_id
def _get_common_dest_location_from_line(self, line_ids):
"""Return the ID of the common location between all lines. If no common
destination was found, return False"""
loc_id = False
line_obj = self.env['claim.line']
line_location = []
for line in line_obj.browse(line_ids):
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
# Get default destination location
def _get_dest_loc(self):
"""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."""
context = self.env.context
if context is None:
context = {}
loc_id = False
picking_type = context.get('picking_type')
partner_id = context.get('partner_id')
if picking_type == 'out' and partner_id:
loc_id = self.env['res.partner'].browse(
partner_id).property_stock_customer.id
elif picking_type == 'in' and partner_id:
# Add the case of return to supplier !
line_ids = self._get_claim_lines()
loc_id = self._get_common_dest_location_from_line(line_ids)
return loc_id
def _get_claim_lines(self):
# TODO use custom states to show buttons of this wizard or not instead
# of raise an error
context = self.env.context
if context is None:
context = {}
line_obj = self.env['claim.line']
if context.get('picking_type') == 'out':
move_field = 'move_out_id'
else:
move_field = 'move_in_id'
good_lines = []
lines = line_obj.search(
[('claim_id', '=', context['active_id'])])
for line in lines:
if not line[move_field] or line[move_field].state == 'cancel':
good_lines.append(line.id)
if not good_lines:
raise Warning(
_('Error'),
_('A picking has already been created for this claim.'))
return good_lines
claim_line_source_location = Many2one(
'stock.location', string='Source Location', required=True,
default=_get_source_loc,
help="Location where the returned products are from.")
claim_line_dest_location = Many2one(
'stock.location', string='Dest. Location', required=True,
default=_get_dest_loc,
help="Location where the system will stock the returned products.")
claim_line_ids = Many2many(
'claim.line',
'claim_line_picking',
'claim_picking_id',
'claim_line_id',
string='Claim lines', default=_get_claim_lines)
def _get_common_partner_from_line(self, line_ids):
"""Return the ID of the common partner between all lines. If no common
partner was found, return False"""
partner_id = False
line_obj = self.env['claim.line']
line_partner = []
for line in line_obj.browse(line_ids):
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
@api.multi
def action_cancel(self):
return {'type': 'ir.actions.act_window_close'}
@api.multi
def action_create_picking(self):
picking_obj = self.env['stock.picking']
picking_type_obj = self.env['stock.picking.type']
context = self.env.context
if context is None:
context = {}
view_obj = self.env['ir.ui.view']
name = 'RMA picking out'
if context.get('picking_type') == 'out':
picking_type_code = 'outgoing'
write_field = 'move_out_id'
note = 'RMA picking out'
else:
picking_type_code = 'incoming'
write_field = 'move_in_id'
if context.get('picking_type'):
note = 'RMA picking ' + str(context.get('picking_type'))
name = note
picking_type_id = picking_type_obj.search([
('code', '=', picking_type_code),
('default_location_dest_id', '=',
self.claim_line_dest_location.id)], limit=1).id
model = 'stock.picking'
view_id = view_obj.search([
('model', '=', model),
('type', '=', 'form')], limit=1).id
claim = self.env['crm.claim'].browse(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 context.get('product_return'):
common_dest_loc_id = self._get_common_dest_location_from_line(
claim_lines.ids)
if not common_dest_loc_id:
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_id = self._get_common_partner_from_line(
claim_lines.ids)
if not common_dest_partner_id:
raise 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 = picking_obj.create(
{'origin': claim.code,
'picking_type_id': picking_type_id,
'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': self.claim_line_source_location.id,
'location_dest_id': self.claim_line_dest_location.id,
'note': note,
'claim_id': claim.id,
})
# Create picking lines
fmt = DEFAULT_SERVER_DATETIME_FORMAT
for line in self.claim_line_ids:
move_id = self.env['stock.move'].create({
'name': line.product_id.name_template,
'priority': '0',
'date': time.strftime(fmt),
'date_expected': time.strftime(fmt),
'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': partner_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,
'location_dest_id': self.claim_line_dest_location.id,
'note': note}).id
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', '=', '%s'), "
"('partner_id', '=', %s)]" % (picking_type_code, 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',
}

View File

@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright 2015 Eezee-It
# 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
@@ -22,31 +22,20 @@
#
##############################################################################
from openerp.models import api, TransientModel
from openerp.fields import Char
from openerp import models, fields, api
class AccountInvoiceRefund(TransientModel):
class AccountInvoiceRefund(models.TransientModel):
_inherit = "account.invoice.refund"
def _get_description(self):
context = self.env.context
if context is None:
context = {}
def _default_description(self):
return self.env.context.get('description', '')
description = context.get('description') or ''
return description
description = Char(default=_get_description)
description = fields.Char(default=_default_description)
@api.one
def compute_refund(self, mode='refund'):
context = self.env.context.copy()
if context is None:
context = {}
if context.get('invoice_ids'):
context['active_ids'] = context.get('invoice_ids')
self = self.with_context(context)
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)

View File

@@ -0,0 +1,236 @@
# -*- 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',
}

View File

@@ -13,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>