mirror of
https://github.com/ForgeFlow/stock-rma.git
synced 2025-01-21 12:57:49 +02:00
[9.0][IMP] rma: several fixes for dropshipping:
* propaget properly partner for picking and move allowing to print packing operations as a dropshiping operation. * main use case for create supplier rma wizard is dropshipping.
This commit is contained in:
@@ -86,6 +86,14 @@ Create an RMA:
|
||||
Order".
|
||||
#. Go back to the RMA. Set the RMA to done if not further action is required.
|
||||
|
||||
Known issues and Roadmap
|
||||
========================
|
||||
|
||||
* Picking operations report in customer RMA dropshipping case is showing
|
||||
"Vendor Address" while it should be "Customer Address".
|
||||
* Dropshipping always counted as a delivery on the smart buttons.
|
||||
* Uninstall hook.
|
||||
* Constraints instead of required fields on rma.order.line.
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
@@ -19,10 +19,9 @@ class ProcurementOrder(models.Model):
|
||||
if self.rma_line_id:
|
||||
line = self.rma_line_id
|
||||
res['rma_line_id'] = line.id
|
||||
if line.delivery_address_id:
|
||||
res['partner_id'] = line.delivery_address_id.id
|
||||
else:
|
||||
res['partner_id'] = line.rma_id.partner_id.id
|
||||
# Propagate partner_dest_id for proper drop-shipment reports.
|
||||
if procurement.partner_dest_id:
|
||||
res['partner_id'] = procurement.partner_dest_id.id
|
||||
dest_loc = self.env["stock.location"].browse([
|
||||
res["location_dest_id"]])[0]
|
||||
if dest_loc.usage == "internal":
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (C) 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# © 2017 Eficent Business and IT Consulting Services S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
@@ -47,7 +47,7 @@ class RmaOrderLine(models.Model):
|
||||
@api.multi
|
||||
def _compute_in_shipment_count(self):
|
||||
for line in self:
|
||||
moves = line.procurement_ids.mapped('move_ids').filtered(
|
||||
moves = line.mapped('move_ids').filtered(
|
||||
lambda m: m.location_dest_id.usage == 'internal')
|
||||
pickings = moves.mapped('picking_id')
|
||||
line.in_shipment_count = len(pickings)
|
||||
@@ -55,7 +55,7 @@ class RmaOrderLine(models.Model):
|
||||
@api.multi
|
||||
def _compute_out_shipment_count(self):
|
||||
for line in self:
|
||||
moves = line.procurement_ids.mapped('move_ids').filtered(
|
||||
moves = line.mapped('move_ids').filtered(
|
||||
lambda m: m.location_dest_id.usage != 'internal')
|
||||
pickings = moves.mapped('picking_id')
|
||||
line.out_shipment_count = len(pickings)
|
||||
@@ -69,11 +69,12 @@ class RmaOrderLine(models.Model):
|
||||
op = ops['=']
|
||||
else:
|
||||
op = ops['!=']
|
||||
for move in rec.procurement_ids.mapped('move_ids').filtered(
|
||||
for move in rec.move_ids.filtered(
|
||||
lambda m: m.state in states and op(m.location_id.usage,
|
||||
rec.type)):
|
||||
qty += product_obj._compute_quantity(
|
||||
move.product_uom_qty, rec.uom_id)
|
||||
qty += product_obj._compute_qty_obj(
|
||||
move.product_uom, move.product_uom_qty,
|
||||
rec.uom_id)
|
||||
return qty
|
||||
|
||||
@api.multi
|
||||
@@ -83,9 +84,11 @@ class RmaOrderLine(models.Model):
|
||||
for rec in self:
|
||||
rec.qty_to_receive = 0.0
|
||||
if rec.receipt_policy == 'ordered':
|
||||
rec.qty_to_receive = rec.product_qty - rec.qty_received
|
||||
elif self.receipt_policy == 'delivered':
|
||||
self.qty_to_receive = rec.qty_delivered - rec.qty_received
|
||||
rec.qty_to_receive = \
|
||||
rec.product_qty - rec.qty_incoming -rec.qty_received
|
||||
elif rec.receipt_policy == 'delivered':
|
||||
rec.qty_to_receive = \
|
||||
rec.qty_delivered - rec.qty_incoming - rec.qty_received
|
||||
|
||||
@api.multi
|
||||
@api.depends('move_ids', 'move_ids.state',
|
||||
@@ -141,9 +144,13 @@ class RmaOrderLine(models.Model):
|
||||
'receipt_policy', 'product_qty', 'type')
|
||||
def _compute_qty_supplier_rma(self):
|
||||
for rec in self:
|
||||
qty = rec._get_supplier_rma_qty()
|
||||
rec.qty_to_supplier_rma = rec.product_qty - qty
|
||||
rec.qty_in_supplier_rma = qty
|
||||
if rec.customer_to_supplier:
|
||||
supplier_rma_qty = rec._get_supplier_rma_qty()
|
||||
rec.qty_to_supplier_rma = rec.product_qty - supplier_rma_qty
|
||||
rec.qty_in_supplier_rma = supplier_rma_qty
|
||||
else:
|
||||
rec.qty_to_supplier_rma = 0.0
|
||||
rec.qty_in_supplier_rma = 0.0
|
||||
|
||||
@api.multi
|
||||
def _compute_procurement_count(self):
|
||||
@@ -227,20 +234,21 @@ class RmaOrderLine(models.Model):
|
||||
procurement_count = fields.Integer(compute=_compute_procurement_count,
|
||||
string='# of Procurements', copy=False)
|
||||
in_shipment_count = fields.Integer(compute=_compute_in_shipment_count,
|
||||
string='# of Shipments', default=0)
|
||||
string='# of Shipments')
|
||||
out_shipment_count = fields.Integer(compute=_compute_out_shipment_count,
|
||||
string='# of Deliveries', default=0)
|
||||
string='# of Deliveries')
|
||||
move_ids = fields.One2many('stock.move', 'rma_line_id',
|
||||
string='Stock Moves', readonly=True,
|
||||
copy=False)
|
||||
procurement_ids = fields.One2many('procurement.order', 'rma_line_id',
|
||||
string='Procurements', readonly=True,
|
||||
copy=False)
|
||||
reference_move_id = fields.Many2one(
|
||||
comodel_name='stock.move', string='Originating Stock Move',
|
||||
copy=False,
|
||||
readonly=True, states={'draft': [('readonly', False)]},
|
||||
)
|
||||
procurement_ids = fields.One2many('procurement.order', 'rma_line_id',
|
||||
string='Procurements', readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
copy=False)
|
||||
currency_id = fields.Many2one('res.currency', string="Currency")
|
||||
company_id = fields.Many2one(
|
||||
comodel_name='res.company', string='Company', required=True,
|
||||
@@ -369,21 +377,21 @@ class RmaOrderLine(models.Model):
|
||||
operation = self.env['rma.operation'].search(
|
||||
[('type', '=', self.type)], limit=1)
|
||||
if not operation:
|
||||
raise ValidationError(_("Please define an operation first."))
|
||||
raise ValidationError("Please define an operation first.")
|
||||
|
||||
if not operation.in_route_id or not operation.out_route_id:
|
||||
route = self.env['stock.location.route'].search(
|
||||
[('rma_selectable', '=', True)], limit=1)
|
||||
if not route:
|
||||
raise ValidationError(_("Please define an RMA route."))
|
||||
raise ValidationError("Please define an RMA route.")
|
||||
|
||||
if not operation.in_warehouse_id or not operation.out_warehouse_id:
|
||||
warehouse = self.env['stock.warehouse'].search(
|
||||
[('company_id', '=', self.company_id.id),
|
||||
('lot_rma_id', '!=', False)], limit=1)
|
||||
if not warehouse:
|
||||
raise ValidationError(_(
|
||||
"Please define a warehouse with a default RMA location."))
|
||||
raise ValidationError(
|
||||
"Please define a warehouse with a default RMA location.")
|
||||
|
||||
data = {
|
||||
'product_id': sm.product_id.id,
|
||||
@@ -409,26 +417,32 @@ class RmaOrderLine(models.Model):
|
||||
@api.onchange('reference_move_id')
|
||||
def _onchange_reference_move_id(self):
|
||||
self.ensure_one()
|
||||
for move in self.reference_move_id:
|
||||
data = self._prepare_rma_line_from_stock_move(move, lot=False)
|
||||
sm = self.reference_move_id
|
||||
if not sm:
|
||||
return
|
||||
if sm.lot_ids:
|
||||
if len(sm.lot_ids) > 1:
|
||||
raise UserError(_('To manage lots use RMA groups.'))
|
||||
else:
|
||||
data = self._prepare_rma_line_from_stock_move(
|
||||
sm, lot=sm.lot_ids[0])
|
||||
self.update(data)
|
||||
else:
|
||||
data = self._prepare_rma_line_from_stock_move(
|
||||
sm, lot=False)
|
||||
self.update(data)
|
||||
self._remove_other_data_origin('reference_move_id')
|
||||
lot_ids = [x.lot_id.id for x in self.move_ids if x.lot_id]
|
||||
return {'domain': {'lot_id': [('id', 'in', lot_ids)]}}
|
||||
self._remove_other_data_origin('reference_move_id')
|
||||
|
||||
@api.multi
|
||||
@api.constrains('reference_move_id', 'partner_id')
|
||||
def _check_move_partner(self):
|
||||
for rec in self:
|
||||
if (rec.reference_move_id and
|
||||
(rec.reference_move_id.partner_id != rec.partner_id) and
|
||||
(rec.reference_move_id.picking_id.partner_id !=
|
||||
rec.partner_id)):
|
||||
raise ValidationError(_(
|
||||
"RMA customer (%s) and originating stock move customer"
|
||||
" (%s) doesn't match." % (
|
||||
rec.reference_move_id.partner_id.name,
|
||||
rec.partner_id.name)))
|
||||
rec.reference_move_id.picking_id.partner_id !=
|
||||
rec.partner_id):
|
||||
raise ValidationError(_(
|
||||
"RMA customer and originating stock move customer "
|
||||
"doesn't match."))
|
||||
|
||||
@api.multi
|
||||
def _remove_other_data_origin(self, exception):
|
||||
@@ -447,8 +461,7 @@ class RmaOrderLine(models.Model):
|
||||
|
||||
@api.multi
|
||||
def action_rma_draft(self):
|
||||
if self.procurement_ids.mapped('move_ids').filtered(
|
||||
lambda m: m.state != 'cancel'):
|
||||
if self.in_shipment_count or self.out_shipment_count:
|
||||
raise UserError(_(
|
||||
"You cannot reset to draft a RMA with related pickings."))
|
||||
self.write({'state': 'draft'})
|
||||
@@ -504,10 +517,8 @@ class RmaOrderLine(models.Model):
|
||||
self.in_warehouse_id.lot_rma_id
|
||||
self.customer_to_supplier = self.operation_id.customer_to_supplier
|
||||
self.supplier_to_customer = self.operation_id.supplier_to_customer
|
||||
if self.operation_id.in_route_id:
|
||||
self.in_route_id = self.operation_id.in_route_id
|
||||
if self.operation_id.out_route_id:
|
||||
self.out_route_id = self.operation_id.out_route_id
|
||||
self.in_route_id = self.operation_id.in_route_id
|
||||
self.out_route_id = self.operation_id.out_route_id
|
||||
return result
|
||||
|
||||
@api.onchange('customer_to_supplier', 'type')
|
||||
@@ -517,12 +528,22 @@ class RmaOrderLine(models.Model):
|
||||
elif self.type == 'customer' and self.supplier_to_customer:
|
||||
self.delivery_policy = 'no'
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_id(self):
|
||||
self.uom_id = self.product_id.uom_id
|
||||
if self.lot_id.product_id != self.product_id:
|
||||
self.lot_id = False
|
||||
if self.product_id:
|
||||
return {'domain': {
|
||||
'lot_id': [('product_id', '=', self.product_id.id)]}}
|
||||
return {'domain': {'lot_id': []}}
|
||||
|
||||
@api.onchange("lot_id")
|
||||
def _onchange_lot_id(self):
|
||||
if self.lot_id and self.reference_move_id:
|
||||
data = self._prepare_rma_line_from_stock_move(
|
||||
self.reference_move_id, lot=self.lot_id)
|
||||
self.update(data)
|
||||
product = self.lot_id.product_id
|
||||
if product:
|
||||
self.product_id = product
|
||||
self.uom_id = product.uom_id
|
||||
|
||||
@api.multi
|
||||
def action_view_in_shipments(self):
|
||||
|
||||
@@ -36,3 +36,10 @@ class StockMove(models.Model):
|
||||
if procurement.rma_line_id:
|
||||
vals['rma_line_id'] = procurement.rma_line_id.id
|
||||
return super(StockMove, self).create(vals)
|
||||
|
||||
@api.model
|
||||
def _prepare_picking_assign(self, move):
|
||||
res = super(StockMove, self)._prepare_picking_assign(move)
|
||||
if move.rma_line_id:
|
||||
res['partner_id'] = move.rma_line_id.partner_id.id or False
|
||||
return res
|
||||
|
||||
@@ -1,87 +1,90 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="rma_operation_tree" model="ir.ui.view">
|
||||
<field name="name">rma.operation.tree</field>
|
||||
<field name="model">rma.operation</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="RMA Operations">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="receipt_policy"/>
|
||||
<field name="delivery_policy"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<data>
|
||||
<record id="rma_operation_tree" model="ir.ui.view">
|
||||
<field name="name">rma.operation.tree</field>
|
||||
<field name="model">rma.operation</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="RMA Operations">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="receipt_policy"/>
|
||||
<field name="delivery_policy"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="rma_operation_form" model="ir.ui.view">
|
||||
<field name="name">rma.operation.form</field>
|
||||
<field name="model">rma.operation</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="RMA Operations">
|
||||
<group colspan="4" col="2">
|
||||
<group name="description"
|
||||
string="Description">
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="type"/>
|
||||
<field name="active"/>
|
||||
<record id="rma_operation_form" model="ir.ui.view">
|
||||
<field name="name">rma.operation.form</field>
|
||||
<field name="model">rma.operation</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="RMA Operations">
|
||||
<group colspan="4" col="2">
|
||||
<group name="description"
|
||||
string="Description">
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="type"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group name="policies"
|
||||
string="Policies">
|
||||
<field name="receipt_policy"/>
|
||||
<field name="delivery_policy"/>
|
||||
</group>
|
||||
<group name="inbound" string="Inbound">
|
||||
<field name="in_route_id"/>
|
||||
<field name="in_warehouse_id"/>
|
||||
<field name="location_id"
|
||||
domain="[('usage', '=', 'internal')]"/>
|
||||
<field name="customer_to_supplier"
|
||||
attrs="{'invisible':[('type', '=', 'supplier')]}"/>
|
||||
</group>
|
||||
<group name="outbound" string="Outbound">
|
||||
<field name="out_route_id"/>
|
||||
<field name="out_warehouse_id"/>
|
||||
<field name="supplier_to_customer"
|
||||
attrs="{'invisible':[('type', '=', 'customer')]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group name="policies"
|
||||
string="Policies">
|
||||
<field name="receipt_policy"/>
|
||||
<field name="delivery_policy"/>
|
||||
</group>
|
||||
<group name="inbound" string="Inbound">
|
||||
<field name="in_route_id"/>
|
||||
<field name="in_warehouse_id"/>
|
||||
<field name="location_id"
|
||||
domain="[('usage', '=', 'internal')]"/>
|
||||
<field name="customer_to_supplier"
|
||||
attrs="{'invisible':[('type', '=', 'supplier')]}"/>
|
||||
<field name="supplier_to_customer"
|
||||
attrs="{'invisible':[('type', '=', 'customer')]}"/>
|
||||
</group>
|
||||
<group name="outbound" string="Outbound">
|
||||
<field name="out_route_id"/>
|
||||
<field name="out_warehouse_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_rma_operation_customer" model="ir.actions.act_window">
|
||||
<field name="name">Customer Operations</field>
|
||||
<field name="res_model">rma.operation</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'default_type': "customer"}</field>
|
||||
<field name="domain">[('type','=', 'customer')]</field>
|
||||
<field name="view_id" ref="rma_operation_tree"/>
|
||||
</record>
|
||||
<record id="action_rma_operation_customer" model="ir.actions.act_window">
|
||||
<field name="name">Customer Operations</field>
|
||||
<field name="res_model">rma.operation</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'default_type': "customer"}</field>
|
||||
<field name="domain">[('type','=', 'customer')]</field>
|
||||
<field name="view_id" ref="rma_operation_tree"/>
|
||||
</record>
|
||||
|
||||
<record id="action_rma_operation_supplier" model="ir.actions.act_window">
|
||||
<field name="name">Supplier Operations</field>
|
||||
<field name="res_model">rma.operation</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'default_type': "supplier"}</field>
|
||||
<field name="domain">[('type','=', 'supplier')]</field>
|
||||
<field name="view_id" ref="rma_operation_tree"/>
|
||||
</record>
|
||||
<record id="action_rma_operation_supplier" model="ir.actions.act_window">
|
||||
<field name="name">Supplier Operations</field>
|
||||
<field name="res_model">rma.operation</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'default_type': "supplier"}</field>
|
||||
<field name="domain">[('type','=', 'supplier')]</field>
|
||||
<field name="view_id" ref="rma_operation_tree"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_rma_operation_customer"
|
||||
name="Customer Operations"
|
||||
groups="rma.group_rma_manager"
|
||||
sequence="35"
|
||||
parent="rma.menu_rma_config"
|
||||
action="action_rma_operation_customer"/>
|
||||
<menuitem id="menu_rma_operation_customer"
|
||||
name="Customer Operations"
|
||||
groups="rma.group_rma_manager"
|
||||
sequence="35"
|
||||
parent="rma.menu_rma_config"
|
||||
action="action_rma_operation_customer"/>
|
||||
|
||||
<menuitem id="menu_rma_operation_supplier"
|
||||
name="Supplier Operations"
|
||||
groups="rma.group_rma_manager"
|
||||
sequence="40"
|
||||
parent="rma.menu_rma_config"
|
||||
action="action_rma_operation_supplier"/>
|
||||
<menuitem id="menu_rma_operation_supplier"
|
||||
name="Supplier Operations"
|
||||
groups="rma.group_rma_manager"
|
||||
sequence="40"
|
||||
parent="rma.menu_rma_config"
|
||||
action="action_rma_operation_supplier"/>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -79,10 +79,10 @@ class RmaMakePicking(models.TransientModel):
|
||||
|
||||
@api.model
|
||||
def _get_address(self, item):
|
||||
if item.line_id.delivery_address_id:
|
||||
delivery_address = item.line_id.delivery_address_id
|
||||
elif item.line_id.customer_to_supplier:
|
||||
if item.line_id.customer_to_supplier:
|
||||
delivery_address = item.line_id.supplier_address_id
|
||||
elif item.line_id.delivery_address_id:
|
||||
delivery_address = item.line_id.delivery_address_id
|
||||
elif item.line_id.partner_id:
|
||||
delivery_address = item.line_id.partner_id
|
||||
else:
|
||||
|
||||
@@ -23,14 +23,28 @@ class RmaLineMakeSupplierRma(models.TransientModel):
|
||||
comodel_name='rma.order', string='Supplier RMA Order Group',
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _get_default_operation(self):
|
||||
"""Dropshipping is the most common use case of this wizard, thus
|
||||
trying to default to a dropshipping operation first."""
|
||||
operation = self.env['rma.operation'].search([
|
||||
('type', '=', 'supplier'),
|
||||
('supplier_to_customer', '=', True)], limit=1)
|
||||
if not operation:
|
||||
operation = self.env['rma.operation'].search(
|
||||
[('type', '=', 'supplier')], limit=1)
|
||||
return operation
|
||||
|
||||
@api.model
|
||||
def _prepare_item(self, line):
|
||||
operation = self._get_default_operation()
|
||||
return {
|
||||
'line_id': line.id,
|
||||
'product_id': line.product_id.id,
|
||||
'name': line.name,
|
||||
'product_qty': line.qty_to_supplier_rma,
|
||||
'uom_id': line.uom_id.id,
|
||||
'operation_id': operation.id if operation else False,
|
||||
}
|
||||
|
||||
@api.model
|
||||
@@ -67,16 +81,17 @@ class RmaLineMakeSupplierRma(models.TransientModel):
|
||||
raise ValidationError(_('Enter a supplier.'))
|
||||
return {
|
||||
'partner_id': self.partner_id.id,
|
||||
'delivery_address_id': self.partner_id.id,
|
||||
'type': 'supplier',
|
||||
'company_id': company.id,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _prepare_supplier_rma_line(self, rma, item):
|
||||
operation = item.line_id.product_id.rma_supplier_operation_id
|
||||
if not operation:
|
||||
operation = self.env['rma.operation'].search(
|
||||
[('type', '=', 'supplier')], limit=1)
|
||||
if item.operation_id:
|
||||
operation = item.operation_id
|
||||
else:
|
||||
operation = self._get_default_operation()
|
||||
if not operation.in_route_id or not operation.out_route_id:
|
||||
route = self.env['stock.location.route'].search(
|
||||
[('rma_selectable', '=', True)], limit=1)
|
||||
@@ -179,3 +194,7 @@ class RmaLineMakeRmaOrderItem(models.TransientModel):
|
||||
uom_id = fields.Many2one('product.uom', string='UoM', readonly=True)
|
||||
product_qty = fields.Float(string='Quantity',
|
||||
digits=dp.get_precision('Product UoS'))
|
||||
operation_id = fields.Many2one(
|
||||
comodel_name="rma.operation", string="Operation",
|
||||
domain=[('type', '=', 'supplier')],
|
||||
)
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
options="{'no_open': true}"/>
|
||||
<field name="product_id"/>
|
||||
<field name="name"/>
|
||||
<field name="operation_id"/>
|
||||
<field name="product_qty"/>
|
||||
<field name="uom_id"
|
||||
groups="product.group_uom"/>
|
||||
|
||||
Reference in New Issue
Block a user