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

[16.0][FW] [14.0] shipment_advice: load a transfer in multiple shipments + list goods that could be loaded (new smart button) #156

Open
wants to merge 2 commits into
base: 16.0
Choose a base branch
from
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
118 changes: 112 additions & 6 deletions shipment_advice/models/shipment_advice.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@
compute="_compute_picking_ids",
string="Loaded transfers",
)
to_validate_picking_ids = fields.One2many(
comodel_name="stock.picking",
compute="_compute_picking_ids",
string="Transfers to validate",
)
loaded_pickings_count = fields.Integer(compute="_compute_count")
loaded_package_ids = fields.One2many(
comodel_name="stock.quant.package",
Expand All @@ -167,6 +172,17 @@
string="Package Levels",
)
loaded_packages_count = fields.Integer(compute="_compute_count")
line_to_load_ids = fields.One2many(
comodel_name="stock.move.line",
compute="_compute_line_to_load_ids",
help=(
"Lines to load in priority.\n"
"If the shipment is planned, it'll return the planned lines.\n"
"If the shipment is not planned, it'll return lines from transfers "
"partially loaded."
),
)
lines_to_load_count = fields.Integer(compute="_compute_count")
carrier_ids = fields.Many2many(
comodel_name="delivery.carrier",
string="Related shipping methods",
Expand Down Expand Up @@ -195,6 +211,69 @@
def _default_run_in_queue_job(self):
return self.env.user.company_id.shipment_advice_run_in_queue_job

def _find_move_lines_domain(self, picking_type_ids=None):
"""Returns the base domain to look for move lines for a given shipment."""
self.ensure_one()
domain = [
("state", "in", ("assigned", "partially_available")),
("picking_code", "=", self.shipment_type),
"|",
("shipment_advice_id", "=", False),
("shipment_advice_id", "=", self.id),
]
# Restrict on picking types if provided
if picking_type_ids:
domain.insert(0, ("picking_id.picking_type_id", "in", picking_type_ids.ids))

Check warning on line 226 in shipment_advice/models/shipment_advice.py

View check run for this annotation

Codecov / codecov/patch

shipment_advice/models/shipment_advice.py#L226

Added line #L226 was not covered by tests
else:
domain.insert(
0,
("picking_id.picking_type_id.warehouse_id", "=", self.warehouse_id.id),
)
# Shipment with planned content, restrict the search to it
if self.planned_move_ids:
domain.append(("move_id.shipment_advice_id", "=", self.id))
# Shipment without planned content, search for all unplanned moves
else:
domain.append(("move_id.shipment_advice_id", "=", False))
# Restrict to shipment carrier delivery types (providers)
if self.carrier_ids:
domain.extend(

Check warning on line 240 in shipment_advice/models/shipment_advice.py

View check run for this annotation

Codecov / codecov/patch

shipment_advice/models/shipment_advice.py#L240

Added line #L240 was not covered by tests
[
"|",
(
"picking_id.carrier_id.delivery_type",
"in",
self.carrier_ids.mapped("delivery_type"),
),
("picking_id.carrier_id", "=", False),
]
)
return domain

@api.depends("planned_move_ids")
@api.depends_context("shipment_picking_type_ids")
def _compute_line_to_load_ids(self):
picking_type_ids = self.env.context.get("shipment_picking_type_ids", [])
for shipment in self:
domain = shipment._find_move_lines_domain(picking_type_ids)
# Restrict to lines not loaded
domain.insert(0, ("shipment_advice_id", "=", False))
# Find lines to load from partially loaded transfers if the shipment
# is not planned.
if not shipment.planned_move_ids:
all_lines_to_load = self.env["stock.move.line"].search(domain)
all_pickings = all_lines_to_load.picking_id
loaded_lines = self.env["stock.move.line"].search(
[
("picking_id", "in", all_pickings.ids),
("id", "not in", all_lines_to_load.ids),
("shipment_advice_id", "!=", False),
]
)
pickings_partially_loaded = loaded_lines.picking_id
domain += [("picking_id", "in", pickings_partially_loaded.ids)]
shipment.line_to_load_ids = self.env["stock.move.line"].search(domain)

def _check_include_package_level(self, package_level):
"""Check if a package level should be listed in the shipment advice.

Expand All @@ -208,11 +287,25 @@
packages = shipment.loaded_move_line_ids.result_package_id
shipment.total_load = sum(packages.mapped("shipping_weight"))

@api.depends("planned_move_ids", "loaded_move_line_ids")
@api.depends(
"planned_move_ids", "loaded_move_line_ids.picking_id.loaded_shipment_advice_ids"
)
def _compute_picking_ids(self):
for shipment in self:
shipment.planned_picking_ids = shipment.planned_move_ids.picking_id
shipment.loaded_picking_ids = shipment.loaded_move_line_ids.picking_id
# Transfers to validate are those having only the current shipment
# advice to process
to_validate_picking_ids = []
for picking in shipment.loaded_move_line_ids.picking_id:
shipments_to_process = picking.loaded_shipment_advice_ids.filtered(
lambda s: s.state not in ("done", "cancel")
)
if shipments_to_process == shipment:
to_validate_picking_ids.append(picking.id)
shipment.to_validate_picking_ids = self.env["stock.picking"].browse(
to_validate_picking_ids
)

@api.depends(
"loaded_move_line_ids.package_level_id.package_id",
Expand All @@ -233,7 +326,7 @@
package_ids
)

@api.depends("planned_picking_ids", "planned_move_ids")
@api.depends("planned_picking_ids", "planned_move_ids", "line_to_load_ids")
def _compute_count(self):
for shipment in self:
shipment.planned_pickings_count = len(shipment.planned_picking_ids)
Expand All @@ -243,6 +336,7 @@
shipment.loaded_move_line_without_package_ids
)
shipment.loaded_packages_count = len(shipment.loaded_package_ids)
shipment.lines_to_load_count = len(shipment.line_to_load_ids)

@api.depends("planned_picking_ids", "loaded_picking_ids")
def _compute_carrier_ids(self):
Expand Down Expand Up @@ -323,7 +417,7 @@
self.ensure_one()
if self.shipment_type == "incoming":
return self.planned_picking_ids
return self.loaded_picking_ids
return self.to_validate_picking_ids

def _action_done(self):
# Validate transfers (create backorders for unprocessed lines)
Expand Down Expand Up @@ -353,13 +447,17 @@
]
),
group(self.delayable(description=self.name)._unplan_undone_moves()),
group(self.delayable(description=self.name)._postprocess_action_done()),
group(
self.delayable(description=self.name)._postprocess_action_done(
backorder_policy
)
),
).delay()
return
for picking in pickings:
self._validate_picking(picking, backorder_policy)
self._unplan_undone_moves()
self._postprocess_action_done()
self._postprocess_action_done(backorder_policy)

def _check_action_done_allowed(self):
for shipment in self:
Expand Down Expand Up @@ -402,12 +500,13 @@
).filtered(lambda m: m.state not in ("cancel", "done") and not m.quantity_done)
moves_to_unplan.shipment_advice_id = False

def _postprocess_action_done(self):
def _postprocess_action_done(self, backorder_policy):
self.ensure_one()
if self.state != "in_process":
return
if self._get_picking_to_process().filtered(
lambda p: p.state not in ("done", "cancel")
and backorder_policy != "leave_open"
):
self.write(
{
Expand Down Expand Up @@ -508,6 +607,13 @@
action["domain"] = [("id", "in", self.loaded_package_ids.ids)]
return action

def button_open_to_load_move_lines(self):
action_xmlid = "stock.stock_move_line_action"
action = self.env["ir.actions.act_window"]._for_xml_id(action_xmlid)
action["domain"] = [("id", "in", self.line_to_load_ids.ids)]
action["context"] = {} # Disable filters
return action

Check warning on line 615 in shipment_advice/models/shipment_advice.py

View check run for this annotation

Codecov / codecov/patch

shipment_advice/models/shipment_advice.py#L611-L615

Added lines #L611 - L615 were not covered by tests

def _domain_open_deliveries_in_progress(self):
self.ensure_one()
domain = []
Expand Down
1 change: 1 addition & 0 deletions shipment_advice/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from . import test_shipment_advice
from . import test_shipment_advice_async
from . import test_shipment_advice_plan
from . import test_shipment_advice_to_load
from . import test_shipment_advice_load
from . import test_shipment_advice_picking_values
from . import test_shipment_advice_unload
Expand Down
2 changes: 1 addition & 1 deletion shipment_advice/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def setUpClass(cls):
cls.product_out1,
20,
)
cls.package = cls.env["stock.quant.package"].create({"name": "PKG_OUT2"})
cls.package = cls.env["stock.quant.package"].create({"name": "PKG_OUT"})
cls._update_qty_in_location(
cls.picking_type_out.default_location_src_id,
cls.product_out2,
Expand Down
101 changes: 98 additions & 3 deletions shipment_advice/tests/test_shipment_advice.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,34 @@ class TestShipmentAdvice(Common):
def setUpClass(cls):
super().setUpClass()

def _prepare_picking_with_two_packages(self):
# Prepare packages & products
package2 = self.env["stock.quant.package"].create({"name": "PKG_OUT2"})
package3 = self.env["stock.quant.package"].create({"name": "PKG_OUT3"})
self.env["stock.quant"]._update_available_quantity(
self.product_out2,
self.picking_type_out.default_location_src_id,
5,
package_id=package2,
)
self.env["stock.quant"]._update_available_quantity(
self.product_out3,
self.picking_type_out.default_location_src_id,
5,
package_id=package3,
)
# Prepare moves (belonging to the same transfer)
group = self.env["procurement.group"].create({})
move_product_out2_2 = self._create_move(
self.picking_type_out, self.product_out2, 5, group
)
self.assertEqual(move_product_out2_2.move_line_ids.package_id, package2)
move_product_out3_2 = self._create_move(
self.picking_type_out, self.product_out3, 5, group
)
self.assertEqual(move_product_out3_2.move_line_ids.package_id, package3)
return move_product_out2_2.picking_id

def test_shipment_advice_confirm(self):
self._check_sequence(self.shipment_advice_out)
with self.assertRaises(UserError):
Expand Down Expand Up @@ -78,8 +106,10 @@ def test_shipment_advice_incoming_done_partial(self):
self.assertEqual(backorder.state, "assigned")

def test_shipment_advice_done_full(self):
"""Validating a shipment (whatever the backorder policy is) should
validate all fully loaded transfers.
"""Validating a shipment validate all fully loaded related transfers.

Whatever the backorder policy is, and if the loaded transfers are linked
to only one in progress shipment.
"""
picking = self.move_product_out1.picking_id
self.progress_shipment_advice(self.shipment_advice_out)
Expand Down Expand Up @@ -108,7 +138,7 @@ def test_shipment_advice_done_backorder_policy_disabled(self):
# Validate the shipment => the transfer is still open
self.shipment_advice_out.action_done()
picking = package_level.picking_id
self.assertEqual(self.shipment_advice_out.state, "error")
self.assertEqual(self.shipment_advice_out.state, "done")
# Check the transfer
self.assertTrue(
all(
Expand All @@ -118,6 +148,42 @@ def test_shipment_advice_done_backorder_policy_disabled(self):
)
self.assertEqual(picking.state, "assigned")

def test_multi_shipment_advice_done_backorder_policy_disabled(self):
"""Load a transfer in multiple shipments and validate them with no BO policy.

The last shipment validated is then responsible of the the transfer validation.

1. Load first package in one shipment advice
2. Validate the first shipment advice: delivery order is not yet validated
3. Load second package in another shipment advice
4. Validate the second shipment advice: delivery order is now well validated
"""
# Disable the backorder policy
company = self.shipment_advice_out.company_id
company.shipment_advice_outgoing_backorder_policy = "leave_open"
# Prepare a transfer to load in two shipment advices
shipment_advice_out2 = self.env["shipment.advice"].create(
{"shipment_type": "outgoing"}
)
picking = self._prepare_picking_with_two_packages()
line1, line2 = picking.move_line_ids
# Load first package in the first shipment advice
pl1 = line1.package_level_id
self.progress_shipment_advice(self.shipment_advice_out)
self.load_records_in_shipment(self.shipment_advice_out, pl1)
# Validate the first shipment advice: delivery order hasn't been validated
self.shipment_advice_out.action_done()
self.assertEqual(self.shipment_advice_out.state, "done")
self.assertEqual(picking.state, "assigned")
# Load second package in the second shipment advice
pl2 = line2.package_level_id
self.progress_shipment_advice(shipment_advice_out2)
self.load_records_in_shipment(shipment_advice_out2, pl2)
# Validate the second shipment advice: delivery order has now been validated
shipment_advice_out2.action_done()
self.assertEqual(shipment_advice_out2.state, "done")
self.assertEqual(picking.state, "done")

def test_shipment_advice_done_backorder_policy_enabled(self):
"""Validating a shipment with the backorder policy enabled should
validate partial transfers and create a backorder.
Expand Down Expand Up @@ -147,6 +213,35 @@ def test_shipment_advice_done_backorder_policy_enabled(self):
)
self.assertEqual(picking2.state, "assigned")

def test_assign_lines_to_multiple_shipment_advices(self):
"""Assign lines of a transfer to different shipment advices.

1. Load two packages in two different shipment advices
2. Validate the first shipment advice: delivery order is not yet validated
3. Validate the second shipment advice: delivery order is now well validated
"""
# Prepare a transfer to load in two shipment advices
shipment_advice_out2 = self.env["shipment.advice"].create(
{"shipment_type": "outgoing"}
)
picking = self._prepare_picking_with_two_packages()
line1, line2 = picking.move_line_ids
# Load packages in different shipment advices
pl1 = line1.package_level_id
self.progress_shipment_advice(self.shipment_advice_out)
self.load_records_in_shipment(self.shipment_advice_out, pl1)
pl2 = line2.package_level_id
self.progress_shipment_advice(shipment_advice_out2)
self.load_records_in_shipment(shipment_advice_out2, pl2)
# Validate the first shipment advice: delivery order hasn't been validated
self.shipment_advice_out.action_done()
self.assertEqual(self.shipment_advice_out.state, "done")
self.assertEqual(picking.state, "assigned")
# Validate the second shipment advice: delivery order has now been validated
shipment_advice_out2.action_done()
self.assertEqual(shipment_advice_out2.state, "done")
self.assertEqual(picking.state, "done")

def test_shipment_advice_cancel(self):
self.progress_shipment_advice(self.shipment_advice_out)
self.shipment_advice_out.action_cancel()
Expand Down
2 changes: 1 addition & 1 deletion shipment_advice/tests/test_shipment_advice_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def test_shipment_advice_done_backorder_policy_disabled(self):
self._asset_jobs_dependency(jobs)
trap.perform_enqueued_jobs()
picking = package_level.picking_id
self.assertEqual(self.shipment_advice_out.state, "error")
self.assertEqual(self.shipment_advice_out.state, "done")
# Check the transfer
self.assertTrue(
all(
Expand Down
Loading
Loading