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

[14.0][IMP] shipment advice: Auto close incoming #132

Merged
merged 3 commits into from
Jul 12, 2024
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
9 changes: 9 additions & 0 deletions shipment_advice/models/res_company.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2021 Camptocamp SA
# Copyright 2024 Michael Tietz (MT Software) <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo import fields, models
Expand All @@ -23,3 +24,11 @@ class ResCompany(models.Model):
"deliveries will be shipped by several trucks."
),
)
shipment_advice_auto_close_incoming = fields.Boolean(
string="Shipment Advice: Auto Close Incoming Advices",
help=(
"This flag indicates if an incoming shipment advice "
"will be automatically set to done "
"if all related moves are done or canceled"
),
)
4 changes: 4 additions & 0 deletions shipment_advice/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2021 Camptocamp SA
# Copyright 2024 Michael Tietz (MT Software) <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo import fields, models
Expand All @@ -10,3 +11,6 @@ class ResConfigSettings(models.TransientModel):
shipment_advice_outgoing_backorder_policy = fields.Selection(
related="company_id.shipment_advice_outgoing_backorder_policy", readonly=False
)
shipment_advice_auto_close_incoming = fields.Boolean(
related="company_id.shipment_advice_auto_close_incoming", readonly=False
)
113 changes: 66 additions & 47 deletions shipment_advice/models/shipment_advice.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2021 Camptocamp SA
# Copyright 2024 Michael Tietz (MT Software) <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo import _, api, fields, models
Expand Down Expand Up @@ -379,63 +380,81 @@ def _lock_records(self, records):
sql = "SELECT id FROM %s WHERE ID IN %%s FOR UPDATE" % records._table
self.env.cr.execute(sql, (tuple(records.ids),), log_exceptions=False)

def action_done(self):
def _close_pickings(self):
"""Validate transfers (create backorders for unprocessed lines)"""
self.ensure_one()
wiz_model = self.env["stock.backorder.confirmation"]
pickings = self.env["stock.picking"]
create_backorder = True
if self.shipment_type == "incoming":
self._lock_records(self.planned_picking_ids)
pickings = self.planned_picking_ids
else:
self._lock_records(self.loaded_picking_ids)
pickings = self.to_validate_picking_ids
create_backorder = (
self.company_id.shipment_advice_outgoing_backorder_policy
== "create_backorder"
)
for picking in pickings:
if picking.state in ("cancel", "done"):
continue
if picking._check_backorder():
if not create_backorder:
continue
wiz = wiz_model.create({})
wiz.pick_ids = picking
wiz.with_context(button_validate_picking_ids=picking.ids).process()
else:
picking._action_done()

def _unplan_loaded_moves(self):
"""Unplan moves that were not loaded and validated"""
moves_to_unplan = self.loaded_move_line_ids.move_id.filtered(
lambda m: m.state not in ("cancel", "done") and not m.quantity_done
)
moves_to_unplan.shipment_advice_id = False

def action_done(self):
shipment_advice_ids_to_validate = []
self = self.with_context(shipment_advice_ignore_auto_close=True)
for shipment in self:
if shipment.state != "in_progress":
raise UserError(
_("Shipment {} is not started, operation aborted.").format(
shipment.name
)
)
# Validate transfers (create backorders for unprocessed lines)
if shipment.shipment_type == "incoming":
self._lock_records(self.planned_picking_ids)
for picking in self.planned_picking_ids:
if picking.state in ("cancel", "done"):
continue
if picking._check_backorder():
wiz = wiz_model.create({})
wiz.pick_ids = picking
wiz.with_context(
button_validate_picking_ids=picking.ids
).process()
else:
picking._action_done()
else:
backorder_policy = (
shipment.company_id.shipment_advice_outgoing_backorder_policy
)
self._lock_records(self.loaded_picking_ids)
if backorder_policy == "create_backorder":
for picking in self.to_validate_picking_ids:
if picking.state in ("cancel", "done"):
continue
if picking._check_backorder():
wiz = wiz_model.create({})
wiz.pick_ids = picking
wiz.with_context(
button_validate_picking_ids=picking.ids
).process()
else:
picking._action_done()
else:
for picking in self.to_validate_picking_ids:
if picking.state in ("cancel", "done"):
continue
if not picking._check_backorder():
# no backorder needed means that all qty_done are
# set to fullfill the need => validate
picking._action_done()
# Unplan moves that were not loaded and validated
moves_to_unplan = self.loaded_move_line_ids.move_id.filtered(
lambda m: m.state not in ("cancel", "done") and not m.quantity_done
)
moves_to_unplan.shipment_advice_id = False
shipment.departure_date = fields.Datetime.now()
shipment.state = "done"
shipment._close_pickings()
if shipment.shipment_type == "outgoing":
shipment._unplan_loaded_moves()
shipment_advice_ids_to_validate.append(shipment.id)
if shipment_advice_ids_to_validate:
self.browse(shipment_advice_ids_to_validate)._action_done()
return True

def _action_done(self):
self.write({"departure_date": fields.Datetime.now(), "state": "done"})

def auto_close_incoming_shipment_advices(self):
"""Set incoming shipment advice to done when all planned moves are processed"""
if self.env.context.get("shipment_advice_ignore_auto_close"):
return
shipment_ids_to_close = []
for shipment in self:
if (
shipment.shipment_type != "incoming"
or not shipment.company_id.shipment_advice_auto_close_incoming
or any(
move.state not in ("cancel", "done")
for move in shipment.planned_move_ids
)
):
continue
shipment_ids_to_close.append(shipment.id)
if shipment_ids_to_close:
self.browse(shipment_ids_to_close)._action_done()

def action_cancel(self):
for shipment in self:
if shipment.state not in ("confirmed", "in_progress"):
Expand Down
11 changes: 11 additions & 0 deletions shipment_advice/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2021 Camptocamp SA
# Copyright 2024 Michael Tietz (MT Software) <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo import fields, models
Expand All @@ -23,3 +24,13 @@ def _prepare_merge_moves_distinct_fields(self):
# Avoid having stock move assign to different shipment merged together
res.append("shipment_advice_id")
return res

def _action_done(self, cancel_backorder=False):
res = super()._action_done(cancel_backorder=cancel_backorder)
res.shipment_advice_id.auto_close_incoming_shipment_advices()
return res

def _action_cancel(self):
res = super()._action_cancel()
self.shipment_advice_id.auto_close_incoming_shipment_advices()
return res
1 change: 1 addition & 0 deletions shipment_advice/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Simone Orsi <[email protected]>
* `Trobz <https://trobz.com>`_:
* Dung Tran <[email protected]>
* Michael Tietz (MT Software) <[email protected]>

Design
~~~~~~
Expand Down
1 change: 1 addition & 0 deletions shipment_advice/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
from . import test_shipment_advice_picking_values
from . import test_shipment_advice_unload
from . import test_shipment_advice_stock_user
from . import test_shipment_advice_auto_close
55 changes: 37 additions & 18 deletions shipment_advice/tests/common.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Copyright 2021 Camptocamp SA
# Copyright 2024 Michael Tietz (MT Software) <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from odoo import fields
from odoo.tests.common import SavepointCase, new_test_user
from odoo.tests.common import Form, SavepointCase, new_test_user


class Common(SavepointCase):
Expand Down Expand Up @@ -118,32 +119,33 @@
move.picking_id.action_assign()
return move

def _confirm_shipment_advice(self, shipment_advice, arrival_date=None):
@classmethod
def confirm_shipment_advice(cls, shipment_advice, arrival_date=None):
if shipment_advice.state != "draft":
return
if arrival_date is None:
arrival_date = fields.Datetime.now()
shipment_advice.arrival_date = arrival_date
shipment_advice.action_confirm()
self.assertEqual(shipment_advice.state, "confirmed")

def _in_progress_shipment_advice(self, shipment_advice, dock=None):
self._confirm_shipment_advice(shipment_advice)
@classmethod
def progress_shipment_advice(cls, shipment_advice, dock=None):
cls.confirm_shipment_advice(shipment_advice)
if shipment_advice.state != "confirmed":
return
shipment_advice.dock_id = dock or self.dock
shipment_advice.dock_id = dock or cls.dock
shipment_advice.action_in_progress()
self.assertEqual(shipment_advice.state, "in_progress")

def _cancel_shipment_advice(self, shipment_advice, dock=None):
self._confirm_shipment_advice(shipment_advice)
@classmethod
def cancel_shipment_advice(cls, shipment_advice, dock=None):
cls.confirm_shipment_advice(shipment_advice)
if shipment_advice.state != "confirmed":
return
shipment_advice.action_cancel()
self.assertEqual(shipment_advice.state, "cancel")

def _plan_records_in_shipment(self, shipment_advice, records, user=None):
wiz_model = self.env["wizard.plan.shipment"].with_context(
@classmethod
def plan_records_in_shipment(cls, shipment_advice, records, user=None):
wiz_model = cls.env["wizard.plan.shipment"].with_context(
active_model=records._name,
active_ids=records.ids,
)
Expand All @@ -153,8 +155,9 @@
wiz.action_plan()
return wiz

def _unplan_records_from_shipment(self, records, user=None):
wiz_model = self.env["wizard.unplan.shipment"].with_context(
@classmethod
def unplan_records_from_shipment(cls, records, user=None):
wiz_model = cls.env["wizard.unplan.shipment"].with_context(
active_model=records._name,
active_ids=records.ids,
)
Expand All @@ -164,9 +167,10 @@
wiz.action_unplan()
return wiz

def _load_records_in_shipment(self, shipment_advice, records, user=None):
@classmethod
def load_records_in_shipment(cls, shipment_advice, records, user=None):
"""Load pickings, move lines or package levels in the givent shipment."""
wiz_model = self.env["wizard.load.shipment"].with_context(
wiz_model = cls.env["wizard.load.shipment"].with_context(
active_model=records._name,
active_ids=records.ids,
)
Expand All @@ -176,12 +180,27 @@
wiz.action_load()
return wiz

def _unload_records_from_shipment(self, shipment_advice, records):
@classmethod
def unload_records_from_shipment(cls, shipment_advice, records):
"""Unload pickings, move lines or package levels from the givent shipment."""
wiz_model = self.env["wizard.unload.shipment"].with_context(
wiz_model = cls.env["wizard.unload.shipment"].with_context(
active_model=records._name,
active_ids=records.ids,
)
wiz = wiz_model.create({})
wiz.action_unload()
return wiz

@classmethod
def validate_picking(cls, picking, qty_done=None):
picking.ensure_one()
for ml in picking.move_line_ids:
ml.qty_done = qty_done or ml.product_uom_qty
action_data = picking.button_validate()
if action_data is True:
return cls.env["stock.picking"]
backorder_wizard = Form(

Check warning on line 202 in shipment_advice/tests/common.py

View check run for this annotation

Codecov / codecov/patch

shipment_advice/tests/common.py#L202

Added line #L202 was not covered by tests
cls.env["stock.backorder.confirmation"].with_context(action_data["context"])
).save()
backorder_wizard.process()
return cls.env["stock.picking"].search([("backorder_id", "=", picking.id)])

Check warning on line 206 in shipment_advice/tests/common.py

View check run for this annotation

Codecov / codecov/patch

shipment_advice/tests/common.py#L205-L206

Added lines #L205 - L206 were not covered by tests
Loading
Loading