Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions sale_stock_ux/models/sale_order_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,16 @@ class SaleOrderLine(models.Model):
copy=False,
default="no",
)

total_reserved_quantity = fields.Float(compute="_compute_total_reserved_quantity")

stock_by_location = fields.Text(compute="_compute_stock_by_location")

def _create_procurements(self, product_qty, procurement_uom, origin, values):
self.ensure_one()
# cancelar remanente seta la cantidad como entregada menos devuelta
# asi que no deberia restar en ese caso
product_qty = product_qty - self.quantity_returned
return super()._create_procurements(product_qty, procurement_uom, origin, values)

@api.depends("product_id", "product_uom_qty")
def _compute_total_reserved_quantity(self):
for line in self:
Expand Down Expand Up @@ -119,7 +124,7 @@ def button_cancel_remaining(self):
# 'You can not cancel remianing qty to deliver because '
# 'there are more product invoiced than the delivered. '
# 'You should correct invoice or ask for a refund'))
rec.product_uom_qty = rec.qty_delivered
rec.product_uom_qty = rec.qty_delivered + rec.quantity_returned
rec.order_id.message_post(
body=_('Cancel remaining call for line "%s" (id %s), line qty updated from %s to %s')
% (rec.name, rec.id, old_product_uom_qty, rec.product_uom_qty)
Expand Down Expand Up @@ -166,9 +171,15 @@ def _compute_quantity_returned(self):
quantity_returned = 0.0
# we use same method as in odoo use to delivery's
if order_line.qty_delivered_method == "stock_move":
# Solo considerar devoluciones REALES del cliente, no contraentregas internas
# Las devoluciones reales deben venir de ubicación 'customer' hacia ubicación no-customer
return_moves = order_line.mapped("move_ids").filtered(
lambda r: (
r.state == "done" and not r.scrap_id and r.location_dest_id.usage != "customer" and r.to_refund
r.state == "done"
and not r.scrapped
and r.location_dest_id.usage != "customer"
and r.location_id.usage == "customer"
and r.to_refund
)
)
# In multi-step deliveries, we need to avoid counting the same return multiple times
Expand Down Expand Up @@ -211,7 +222,11 @@ def _compute_quantity_returned(self):
filters = {
"outgoing_moves": lambda m: m.location_dest_id.usage == "customer"
and (not m.origin_returned_move_id or (m.origin_returned_move_id and m.to_refund)),
"incoming_moves": lambda m: m.location_dest_id.usage != "customer" and m.to_refund,
"incoming_moves": lambda m: (
m.location_dest_id.usage != "customer"
and m.location_id.usage == "customer"
and m.to_refund
),
}
order_qty = order_line.product_uom._compute_quantity(
order_line.product_uom_qty, relevant_bom.product_uom_id
Expand All @@ -230,23 +245,23 @@ def _compute_quantity_returned(self):
quantity_returned = 0.0
order_line.quantity_returned = quantity_returned

@api.depends("quantity_returned")
@api.depends("quantity_returned", "move_ids.state", "move_ids.product_uom_qty")
def _compute_qty_to_invoice(self):
"""
Modificamos la funcion original para que si el producto es segun lo
pedido, para que funcione el reembolo hacemos que la cantidad a
pedido, para que funcione el reembolso hacemos que la cantidad a
facturar reste la cantidad devuelta.
NOTA: solo lo hacemos si policy "order" porque en policy "delivered"
odoo ya lo descuenta a la cantidad entregada y automáticamente lo
termina facturando
"""
super()._compute_qty_to_invoice()
for line in self:
# igual que por defecto, si no en estos estados, no hay a facturar
if line.order_id.state not in ["sale", "done"]:
continue
if line.product_id.invoice_policy == "order":
line.qty_to_invoice = line.product_uom_qty - line.quantity_returned - line.qty_invoiced
# Simplemente usar quantity_returned que ya considera todas las devoluciones
# incluyendo kits, dropship, etc. y excluye cancelaciones de remanente
qty_to_invoice_corrected = line.product_uom_qty - line.quantity_returned - line.qty_invoiced
line.qty_to_invoice = qty_to_invoice_corrected

@api.depends(
"order_id.force_invoiced_status", "state", "product_uom_qty", "qty_delivered", "qty_to_invoice", "qty_invoiced"
Expand Down
21 changes: 14 additions & 7 deletions sale_stock_ux/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class StockMove(models.Model):
_inherit = "stock.move"

sale_id = fields.Many2one(
related="sale_line_id.order_id",
related="group_id.sale_id",
)
is_exchange_move = fields.Boolean()

Expand All @@ -26,14 +26,21 @@ def create(self, vals_list):
Agregamos un HACK para que si esta instalado secondary unit sea recomputada en la creacion de
movimientos de stock.
TODO: Solo deberia impactar en movimientos de salida (uno o mas pasos)
TODO: Esto puede ser un problema si usamos unidades secundarias independientes pero por ahora
lo dejamos asi porque calcular cuanto de una unidad secundaria corresponde a la cantidad devuelta
es complejo.
"""
for vals in vals_list:
if vals.get("sale_line_id"):
sale_line_qty_ret = self.env["sale.order.line"].browse(vals["sale_line_id"]).quantity_returned
vals["product_uom_qty"] -= sale_line_qty_ret
if "secondary_uom_qty" in vals:
del vals["secondary_uom_qty"]
# # Nos aseguramos antes de restar que la rule_id es la primera de la ruta que estoy planificando
# is_first_rule = self.env["stock.rule"].browse(vals.get("rule_id")).route_id.rule_ids[:1].id == vals.get(
# "rule_id"
# )

# if vals.get("sale_line_id") and not vals.get("origin_returned_move_id") and is_first_rule:
# sale_line_qty_ret = self.env["sale.order.line"].browse(vals["sale_line_id"]).quantity_returned
# vals["product_uom_qty"] -= sale_line_qty_ret
if vals.get("sale_line_id") and vals.get("secondary_uom_qty"):
del vals["secondary_uom_qty"]
return super().create(vals_list)

def _get_new_picking_values(self):
Expand All @@ -42,7 +49,7 @@ def _get_new_picking_values(self):
"""
res = super()._get_new_picking_values()
values = {}
sale = self.mapped("sale_line_id.order_id")
sale = self.mapped("group_id.sale_id")
propagate_internal_notes = (
self.env["ir.config_parameter"].sudo().get_param("sale.propagate_internal_notes") == "True"
)
Expand Down