Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[13.0][FIX] purchase_sale_stock_inter_company: Allow reception with serial tracking and partial quantity (Dropshipping) #733

Merged
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
1 change: 1 addition & 0 deletions purchase_sale_stock_inter_company/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
from . import res_company
from . import res_config
from . import stock_picking
from . import stock_move_line
99 changes: 99 additions & 0 deletions purchase_sale_stock_inter_company/models/stock_move_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from odoo import api, models


class StockMoveLine(models.Model):
_inherit = "stock.move.line"

@api.model_create_multi
def create(self, vals_list):
new_move_lines = super().create(vals_list)
for move_line in new_move_lines.filtered(lambda x: x.state == "done"):
po_moves = move_line._get_stock_moves_to_sync()
for po_move in po_moves:
po_move_line_vals = po_move._prepare_move_line_vals(
quantity=move_line.qty_done
)
if move_line.lot_id:
dest_lot = move_line._get_or_create_lot_intercompany(
po_move.company_id
)
po_move_line_vals["lot_id"] = dest_lot.id
po_move_line_vals["qty_done"] = move_line.qty_done
self.sudo().create(po_move_line_vals)
return new_move_lines

def write(self, vals):
moves_to_sync = {}
fields_to_sync = self._get_fields_to_sync_intercompany()
if fields_to_sync.intersection(set(vals.keys())):
for move_line in self:
purchase_line_origin = (
move_line.move_id.sale_line_id.sudo().auto_purchase_line_id
)
if move_line.state == "done" and purchase_line_origin:
moves_to_sync.setdefault(move_line, move_line.lot_id.name or "")
res = super().write(vals)
for move_line, lot_name in moves_to_sync.items():
move_line._sync_intercompany_move(lot_name, vals)
return res

def _sync_intercompany_move(self, lot_name, vals):
self.ensure_one()
fields_to_sync = self._get_fields_to_sync_intercompany()
po_moves = self._get_stock_moves_to_sync()
for po_move in po_moves:
po_move_line = po_move.move_line_ids.filtered(
lambda x: not self.lot_id or x.lot_id.name == lot_name
)
if not po_move_line:
continue
vals_to_write = {}
for field in fields_to_sync:
if field not in vals:
continue
field_value = self[field]
if field == "lot_id" and field_value:
dest_lot = self._get_or_create_lot_intercompany(
po_move_line.company_id
)
field_value = dest_lot.id
vals_to_write[field] = field_value
if vals_to_write:
po_move_line.write(vals_to_write)

def _get_stock_moves_to_sync(self):
"""
Get the stock moves that need to be synced with the intercompany move.
"""
self.ensure_one()
purchase_line_origin = self.move_id.sale_line_id.sudo().auto_purchase_line_id
po_moves = self.env["stock.move"]
if purchase_line_origin:
po_moves = purchase_line_origin.move_ids.filtered(
lambda m: m.picking_id
== self.move_id.picking_id.intercompany_picking_id
and m.product_id == self.product_id
)
return po_moves

@api.model
def _get_fields_to_sync_intercompany(self):
return {"qty_done", "lot_id"}

def _get_or_create_lot_intercompany(self, dest_company):
# search if the same lot exists in destination company
self.ensure_one()
ProductionLot = self.env["stock.production.lot"].sudo()
lot = self.lot_id
dest_lot = ProductionLot.search(
[
("product_id", "=", lot.product_id.id),
("name", "=", lot.name),
("company_id", "=", dest_company.id),
],
limit=1,
)
if not dest_lot:
# if it doesn't exist, create it by copying from original company
dest_lot = lot.sudo().copy({"company_id": dest_company.id})
return dest_lot
64 changes: 29 additions & 35 deletions purchase_sale_stock_inter_company/models/stock_picking.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
# Copyright 2023 Tecnativa - Carolina Fernandez
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import _, fields, models
from odoo.exceptions import UserError
from odoo import fields, models


class StockPicking(models.Model):
_inherit = "stock.picking"

intercompany_picking_id = fields.Many2one(comodel_name="stock.picking")
intercompany_picking_id = fields.Many2one(comodel_name="stock.picking", copy=False)

def action_done(self):
for pick in self.filtered(
Expand All @@ -19,46 +18,41 @@ def action_done(self):
purchase = pick.sale_id.auto_purchase_order_id
if not purchase:
continue
purchase.picking_ids.write({"intercompany_picking_id": pick.id})
if not pick.intercompany_picking_id and purchase.picking_ids[0]:
pick.write({"intercompany_picking_id": purchase.picking_ids[0]})
po_picking_pending = purchase.picking_ids.filtered(
lambda x: x.state not in ["done", "cancel"]
)
po_picking_pending.intercompany_picking_id = pick.id
if not pick.intercompany_picking_id and po_picking_pending[0]:
pick.intercompany_picking_id = po_picking_pending[0]
for move in pick.move_lines:
move_lines = move.move_line_ids
po_move_lines = move.sale_line_id.auto_purchase_line_id.move_ids.filtered(
move_lines = move.move_line_ids.filtered(lambda x: x.qty_done > 0)
po_move_pending = move.sale_line_id.auto_purchase_line_id.move_ids.filtered(
lambda x, ic_pick=pick.intercompany_picking_id: x.picking_id
== ic_pick
).mapped(
"move_line_ids"
and x.state not in ["done", "cancel"]
)
if not len(move_lines) == len(po_move_lines):
raise UserError(
_(
"Mismatch between move lines with the "
"corresponding PO %s for assigning "
"quantities and lots from %s for product %s"
)
% (purchase.name, pick.name, move.product_id.name)
)
po_move_lines = po_move_pending.mapped("move_line_ids")
move_line_diff = len(move_lines) - len(po_move_lines)
# generate new move lines if needed
# example: In purchase order of C1, we have 2 move lines
# and in reception of C2, we have 3 move lines(with lot or serial number)
# then we need to create 1 more move line in purchase order of C1
if move_line_diff > 0:
new_move_line_vals = []
for _index in range(move_line_diff):
vals = po_move_pending._prepare_move_line_vals()
new_move_line_vals.append(vals)
po_move_lines |= po_move_lines.create(new_move_line_vals)
# check and assign lots here
# if len(move_lines) != (po_move_lines)
# the zip will stop at the shortest list(only with qty_done > 0)
# list(zip([1, 2], [1, 2, 3, 4])) = [(1, 1), (2, 2)]
# list(zip([1, 2, 3, 4], [1, 2])) = [(1, 1), (2, 2)]
for ml, po_ml in zip(move_lines, po_move_lines):
lot_id = ml.lot_id
if not lot_id:
continue
# search if the same lot exists in destination company
dest_lot_id = (
self.env["stock.production.lot"]
.sudo()
.search(
[
("product_id", "=", lot_id.product_id.id),
("name", "=", lot_id.name),
("company_id", "=", po_ml.company_id.id),
],
limit=1,
)
)
if not dest_lot_id:
# if it doesn't exist, create it by copying from original company
dest_lot_id = lot_id.copy({"company_id": po_ml.company_id.id})
po_ml.lot_id = dest_lot_id
dest_lot = ml._get_or_create_lot_intercompany(po_ml.company_id)
po_ml.lot_id = dest_lot
return super().action_done()
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ def setUpClass(cls):
cls.serial_3 = cls._create_serial_and_quant(
cls.stockable_product_serial, "333", cls.company_b
)
cls.serial_4 = cls._create_serial_and_quant(
cls.stockable_product_serial, "444", cls.company_b
)
cls.serial_5 = cls._create_serial_and_quant(
cls.stockable_product_serial, "555", cls.company_b
)

def test_deliver_to_warehouse_a(self):
self.purchase_company_a.picking_type_id = self.warehouse_a.in_type_id
Expand Down Expand Up @@ -208,3 +214,21 @@ def test_sync_picking_lot(self):
po_lots,
msg="Serial 333 already existed, a new one shouldn't have been created",
)
# create a new lot in the picking done
move_line_vals = so_move._prepare_move_line_vals()
move_line_vals.update({"lot_id": self.serial_4.id, "qty_done": 1})
new_move_line = self.env["stock.move.line"].create(move_line_vals)
self.assertIn(
self.serial_4.name,
po_picking_id.mapped("move_lines.move_line_ids.lot_id.name"),
)
# change the lot in the picking done
new_move_line.lot_id = self.serial_5
self.assertIn(
self.serial_5.name,
po_picking_id.mapped("move_lines.move_line_ids.lot_id.name"),
)
self.assertNotIn(
self.serial_4.name,
po_picking_id.mapped("move_lines.move_line_ids.lot_id.name"),
)
Loading