odoo-addon-shopfloor 16.0.1.0.0.24__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo/addons/shopfloor/README.rst +160 -0
- odoo/addons/shopfloor/__init__.py +4 -0
- odoo/addons/shopfloor/__manifest__.py +65 -0
- odoo/addons/shopfloor/actions/__init__.py +15 -0
- odoo/addons/shopfloor/actions/change_package_lot.py +164 -0
- odoo/addons/shopfloor/actions/completion_info.py +42 -0
- odoo/addons/shopfloor/actions/data.py +329 -0
- odoo/addons/shopfloor/actions/data_detail.py +154 -0
- odoo/addons/shopfloor/actions/inventory.py +150 -0
- odoo/addons/shopfloor/actions/location_content_transfer_sorter.py +89 -0
- odoo/addons/shopfloor/actions/message.py +846 -0
- odoo/addons/shopfloor/actions/move_line_search.py +119 -0
- odoo/addons/shopfloor/actions/packaging.py +59 -0
- odoo/addons/shopfloor/actions/savepoint.py +44 -0
- odoo/addons/shopfloor/actions/schema.py +182 -0
- odoo/addons/shopfloor/actions/schema_detail.py +98 -0
- odoo/addons/shopfloor/actions/search.py +187 -0
- odoo/addons/shopfloor/actions/stock.py +239 -0
- odoo/addons/shopfloor/actions/stock_unreserve.py +66 -0
- odoo/addons/shopfloor/components/__init__.py +5 -0
- odoo/addons/shopfloor/components/scan_handler_location.py +26 -0
- odoo/addons/shopfloor/components/scan_handler_lot.py +26 -0
- odoo/addons/shopfloor/components/scan_handler_package.py +26 -0
- odoo/addons/shopfloor/components/scan_handler_product.py +26 -0
- odoo/addons/shopfloor/components/scan_handler_transfer.py +26 -0
- odoo/addons/shopfloor/data/shopfloor_scenario_data.xml +73 -0
- odoo/addons/shopfloor/demo/shopfloor_app_demo.xml +12 -0
- odoo/addons/shopfloor/demo/shopfloor_menu_demo.xml +64 -0
- odoo/addons/shopfloor/demo/shopfloor_profile_demo.xml +8 -0
- odoo/addons/shopfloor/demo/stock_picking_type_demo.xml +93 -0
- odoo/addons/shopfloor/docs/checkout_diag_seq.plantuml +61 -0
- odoo/addons/shopfloor/docs/checkout_diag_seq.png +0 -0
- odoo/addons/shopfloor/docs/cluster_picking_diag_seq.plantuml +112 -0
- odoo/addons/shopfloor/docs/cluster_picking_diag_seq.png +0 -0
- odoo/addons/shopfloor/docs/delivery_diag_seq.plantuml +56 -0
- odoo/addons/shopfloor/docs/delivery_diag_seq.png +0 -0
- odoo/addons/shopfloor/docs/location_content_transfer_diag_seq.plantuml +66 -0
- odoo/addons/shopfloor/docs/location_content_transfer_diag_seq.png +0 -0
- odoo/addons/shopfloor/docs/oca_logo.png +0 -0
- odoo/addons/shopfloor/docs/single_pack_transfer_diag_seq.plantuml +36 -0
- odoo/addons/shopfloor/docs/single_pack_transfer_diag_seq.png +0 -0
- odoo/addons/shopfloor/docs/zone_picking_diag_seq.plantuml +85 -0
- odoo/addons/shopfloor/docs/zone_picking_diag_seq.png +0 -0
- odoo/addons/shopfloor/exceptions.py +6 -0
- odoo/addons/shopfloor/i18n/ca.po +1802 -0
- odoo/addons/shopfloor/i18n/de.po +1791 -0
- odoo/addons/shopfloor/i18n/es_AR.po +2147 -0
- odoo/addons/shopfloor/i18n/pt_BR.po +1791 -0
- odoo/addons/shopfloor/i18n/shopfloor.pot +1877 -0
- odoo/addons/shopfloor/models/__init__.py +12 -0
- odoo/addons/shopfloor/models/priority_postpone_mixin.py +41 -0
- odoo/addons/shopfloor/models/shopfloor_app.py +9 -0
- odoo/addons/shopfloor/models/shopfloor_menu.py +436 -0
- odoo/addons/shopfloor/models/stock_location.py +76 -0
- odoo/addons/shopfloor/models/stock_move.py +119 -0
- odoo/addons/shopfloor/models/stock_move_line.py +307 -0
- odoo/addons/shopfloor/models/stock_package_level.py +50 -0
- odoo/addons/shopfloor/models/stock_picking.py +118 -0
- odoo/addons/shopfloor/models/stock_picking_batch.py +41 -0
- odoo/addons/shopfloor/models/stock_picking_type.py +26 -0
- odoo/addons/shopfloor/models/stock_quant.py +31 -0
- odoo/addons/shopfloor/models/stock_quant_package.py +101 -0
- odoo/addons/shopfloor/readme/CONTRIBUTORS.rst +18 -0
- odoo/addons/shopfloor/readme/CREDITS.rst +5 -0
- odoo/addons/shopfloor/readme/DESCRIPTION.rst +17 -0
- odoo/addons/shopfloor/readme/HISTORY.rst +4 -0
- odoo/addons/shopfloor/readme/ROADMAP.rst +4 -0
- odoo/addons/shopfloor/readme/USAGE.rst +6 -0
- odoo/addons/shopfloor/security/groups.xml +17 -0
- odoo/addons/shopfloor/services/__init__.py +16 -0
- odoo/addons/shopfloor/services/checkout.py +1763 -0
- odoo/addons/shopfloor/services/cluster_picking.py +1628 -0
- odoo/addons/shopfloor/services/delivery.py +828 -0
- odoo/addons/shopfloor/services/forms/__init__.py +1 -0
- odoo/addons/shopfloor/services/forms/picking_form.py +78 -0
- odoo/addons/shopfloor/services/location_content_transfer.py +1194 -0
- odoo/addons/shopfloor/services/menu.py +60 -0
- odoo/addons/shopfloor/services/picking_batch.py +126 -0
- odoo/addons/shopfloor/services/service.py +101 -0
- odoo/addons/shopfloor/services/single_pack_transfer.py +366 -0
- odoo/addons/shopfloor/services/zone_picking.py +1938 -0
- odoo/addons/shopfloor/static/description/icon.png +0 -0
- odoo/addons/shopfloor/static/description/index.html +500 -0
- odoo/addons/shopfloor/tests/__init__.py +83 -0
- odoo/addons/shopfloor/tests/common.py +324 -0
- odoo/addons/shopfloor/tests/models.py +29 -0
- odoo/addons/shopfloor/tests/test_actions_change_package_lot.py +1175 -0
- odoo/addons/shopfloor/tests/test_actions_data.py +376 -0
- odoo/addons/shopfloor/tests/test_actions_data_base.py +244 -0
- odoo/addons/shopfloor/tests/test_actions_data_detail.py +322 -0
- odoo/addons/shopfloor/tests/test_actions_search.py +248 -0
- odoo/addons/shopfloor/tests/test_actions_stock.py +48 -0
- odoo/addons/shopfloor/tests/test_checkout_auto_post.py +67 -0
- odoo/addons/shopfloor/tests/test_checkout_base.py +81 -0
- odoo/addons/shopfloor/tests/test_checkout_cancel_line.py +154 -0
- odoo/addons/shopfloor/tests/test_checkout_change_packaging.py +184 -0
- odoo/addons/shopfloor/tests/test_checkout_done.py +133 -0
- odoo/addons/shopfloor/tests/test_checkout_list_delivery_packaging.py +131 -0
- odoo/addons/shopfloor/tests/test_checkout_list_package.py +327 -0
- odoo/addons/shopfloor/tests/test_checkout_new_package.py +88 -0
- odoo/addons/shopfloor/tests/test_checkout_no_package.py +95 -0
- odoo/addons/shopfloor/tests/test_checkout_scan.py +174 -0
- odoo/addons/shopfloor/tests/test_checkout_scan_line.py +377 -0
- odoo/addons/shopfloor/tests/test_checkout_scan_line_base.py +25 -0
- odoo/addons/shopfloor/tests/test_checkout_scan_line_no_prefill_qty.py +91 -0
- odoo/addons/shopfloor/tests/test_checkout_scan_package_action.py +451 -0
- odoo/addons/shopfloor/tests/test_checkout_scan_package_action_no_prefill_qty.py +107 -0
- odoo/addons/shopfloor/tests/test_checkout_select.py +74 -0
- odoo/addons/shopfloor/tests/test_checkout_select_line.py +130 -0
- odoo/addons/shopfloor/tests/test_checkout_select_package_base.py +64 -0
- odoo/addons/shopfloor/tests/test_checkout_set_qty.py +257 -0
- odoo/addons/shopfloor/tests/test_checkout_summary.py +69 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_base.py +83 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_batch.py +109 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_change_pack_lot.py +111 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_is_zero.py +98 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_scan_destination.py +376 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_scan_destination_no_prefill_qty.py +115 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_scan_line.py +402 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_scan_line_location_or_pack_first.py +114 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_scan_line_no_prefill_qty.py +70 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_select.py +387 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_skip.py +90 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_stock_issue.py +364 -0
- odoo/addons/shopfloor/tests/test_cluster_picking_unload.py +911 -0
- odoo/addons/shopfloor/tests/test_delivery_base.py +155 -0
- odoo/addons/shopfloor/tests/test_delivery_done.py +108 -0
- odoo/addons/shopfloor/tests/test_delivery_list_stock_picking.py +49 -0
- odoo/addons/shopfloor/tests/test_delivery_reset_qty_done_line.py +119 -0
- odoo/addons/shopfloor/tests/test_delivery_reset_qty_done_pack.py +107 -0
- odoo/addons/shopfloor/tests/test_delivery_scan_deliver.py +557 -0
- odoo/addons/shopfloor/tests/test_delivery_select.py +38 -0
- odoo/addons/shopfloor/tests/test_delivery_set_qty_done_line.py +91 -0
- odoo/addons/shopfloor/tests/test_delivery_set_qty_done_pack.py +135 -0
- odoo/addons/shopfloor/tests/test_delivery_sublocation.py +180 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_base.py +136 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_get_work.py +125 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_mix.py +509 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_putaway.py +143 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_scan_location.py +34 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_set_destination_all.py +343 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_set_destination_package_or_line.py +1074 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_single.py +748 -0
- odoo/addons/shopfloor/tests/test_location_content_transfer_start.py +359 -0
- odoo/addons/shopfloor/tests/test_menu_base.py +261 -0
- odoo/addons/shopfloor/tests/test_menu_counters.py +61 -0
- odoo/addons/shopfloor/tests/test_misc.py +25 -0
- odoo/addons/shopfloor/tests/test_move_action_assign.py +87 -0
- odoo/addons/shopfloor/tests/test_openapi.py +21 -0
- odoo/addons/shopfloor/tests/test_picking_form.py +62 -0
- odoo/addons/shopfloor/tests/test_scan_anything.py +49 -0
- odoo/addons/shopfloor/tests/test_single_pack_transfer.py +1121 -0
- odoo/addons/shopfloor/tests/test_single_pack_transfer_base.py +32 -0
- odoo/addons/shopfloor/tests/test_single_pack_transfer_putaway.py +104 -0
- odoo/addons/shopfloor/tests/test_stock_split.py +204 -0
- odoo/addons/shopfloor/tests/test_user.py +42 -0
- odoo/addons/shopfloor/tests/test_zone_picking_base.py +608 -0
- odoo/addons/shopfloor/tests/test_zone_picking_change_pack_lot.py +140 -0
- odoo/addons/shopfloor/tests/test_zone_picking_select_line.py +723 -0
- odoo/addons/shopfloor/tests/test_zone_picking_select_line_first_scan_location.py +207 -0
- odoo/addons/shopfloor/tests/test_zone_picking_select_line_first_scan_location.py.bak +202 -0
- odoo/addons/shopfloor/tests/test_zone_picking_select_line_no_prefill_qty.py +107 -0
- odoo/addons/shopfloor/tests/test_zone_picking_select_picking_type.py +26 -0
- odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination.py +643 -0
- odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination_no_prefill_qty.py +146 -0
- odoo/addons/shopfloor/tests/test_zone_picking_set_line_destination_pick_pack.py +241 -0
- odoo/addons/shopfloor/tests/test_zone_picking_start.py +206 -0
- odoo/addons/shopfloor/tests/test_zone_picking_stock_issue.py +121 -0
- odoo/addons/shopfloor/tests/test_zone_picking_unload_all.py +353 -0
- odoo/addons/shopfloor/tests/test_zone_picking_unload_buffer_lines.py +113 -0
- odoo/addons/shopfloor/tests/test_zone_picking_unload_set_destination.py +374 -0
- odoo/addons/shopfloor/tests/test_zone_picking_unload_single.py +123 -0
- odoo/addons/shopfloor/tests/test_zone_picking_zero_check.py +43 -0
- odoo/addons/shopfloor/utils.py +13 -0
- odoo/addons/shopfloor/views/shopfloor_menu.xml +167 -0
- odoo/addons/shopfloor/views/stock_location.xml +20 -0
- odoo/addons/shopfloor/views/stock_move_line.xml +52 -0
- odoo/addons/shopfloor/views/stock_picking_type.xml +19 -0
- odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/METADATA +192 -0
- odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/RECORD +182 -0
- odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/WHEEL +5 -0
- odoo_addon_shopfloor-16.0.1.0.0.24.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1175 @@
|
|
1
|
+
# Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
|
2
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
3
|
+
from odoo.tests.common import Form
|
4
|
+
|
5
|
+
from .common import CommonCase
|
6
|
+
|
7
|
+
|
8
|
+
# pylint: disable=missing-return
|
9
|
+
class TestActionsChangePackageLot(CommonCase):
|
10
|
+
"""Tests covering changing a package on a move line"""
|
11
|
+
|
12
|
+
@classmethod
|
13
|
+
def setUpClass(cls):
|
14
|
+
super().setUpClass()
|
15
|
+
with cls.work_on_actions(cls) as work:
|
16
|
+
cls.change_package_lot = work.component(usage="change.package.lot")
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
def setUpClassVars(cls):
|
20
|
+
super().setUpClassVars()
|
21
|
+
cls.wh = cls.env.ref("stock.warehouse0")
|
22
|
+
cls.picking_type = cls.wh.out_type_id
|
23
|
+
cls.picking_type.sudo().show_entire_packs = True
|
24
|
+
|
25
|
+
def _create_picking_with_package_level(self, packages):
|
26
|
+
picking_form = Form(
|
27
|
+
self.env["stock.picking"].with_context(force_detailed_view=True)
|
28
|
+
)
|
29
|
+
picking_form.partner_id = self.customer
|
30
|
+
picking_form.origin = "test"
|
31
|
+
picking_form.picking_type_id = self.picking_type
|
32
|
+
picking_form.location_id = self.stock_location
|
33
|
+
for package in packages:
|
34
|
+
with picking_form.package_level_ids_details.new() as move:
|
35
|
+
move.package_id = package
|
36
|
+
picking = picking_form.save()
|
37
|
+
picking.action_confirm()
|
38
|
+
picking.action_assign()
|
39
|
+
return picking
|
40
|
+
|
41
|
+
def assert_quant_reserved_qty(self, move_line, qty_func, package=None, lot=None):
|
42
|
+
domain = [
|
43
|
+
("location_id", "=", move_line.location_id.id),
|
44
|
+
("product_id", "=", move_line.product_id.id),
|
45
|
+
]
|
46
|
+
if package:
|
47
|
+
domain.append(("package_id", "=", package.id))
|
48
|
+
if lot:
|
49
|
+
domain.append(("lot_id", "=", lot.id))
|
50
|
+
quant = self.env["stock.quant"].search(domain)
|
51
|
+
self.assertEqual(quant.reserved_quantity, qty_func())
|
52
|
+
|
53
|
+
def assert_quant_package_qty(self, location, package, qty_func):
|
54
|
+
quant = self.env["stock.quant"].search(
|
55
|
+
[("location_id", "=", location.id), ("package_id", "=", package.id)]
|
56
|
+
)
|
57
|
+
self.assertEqual(quant.quantity, qty_func())
|
58
|
+
|
59
|
+
@staticmethod
|
60
|
+
def unreachable_func(move_line, message=None):
|
61
|
+
raise AssertionError("should not reach this function")
|
62
|
+
|
63
|
+
def test_change_lot_ok(self):
|
64
|
+
initial_lot = self._create_lot(self.product_a)
|
65
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=initial_lot)
|
66
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
67
|
+
picking.action_assign()
|
68
|
+
line = picking.move_line_ids
|
69
|
+
source_location = line.location_id
|
70
|
+
new_lot = self._create_lot(self.product_a)
|
71
|
+
# ensure we have our new package in the same location
|
72
|
+
self._update_qty_in_location(source_location, line.product_id, 10, lot=new_lot)
|
73
|
+
self.change_package_lot.change_lot(
|
74
|
+
line,
|
75
|
+
new_lot,
|
76
|
+
# success callback
|
77
|
+
lambda move_line, message=None: self.assertEqual(
|
78
|
+
message, self.msg_store.lot_replaced_by_lot(initial_lot, new_lot)
|
79
|
+
),
|
80
|
+
# failure callback
|
81
|
+
self.unreachable_func,
|
82
|
+
)
|
83
|
+
self.assertRecordValues(line, [{"lot_id": new_lot.id}])
|
84
|
+
# check that reservations have been updated
|
85
|
+
self.assert_quant_reserved_qty(line, lambda: 0, lot=initial_lot)
|
86
|
+
self.assert_quant_reserved_qty(line, lambda: line.reserved_qty, lot=new_lot)
|
87
|
+
|
88
|
+
def test_change_lot_less_quantity_ok(self):
|
89
|
+
initial_lot = self._create_lot(self.product_a)
|
90
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=initial_lot)
|
91
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
92
|
+
picking.action_assign()
|
93
|
+
line = picking.move_line_ids
|
94
|
+
source_location = line.location_id
|
95
|
+
new_lot = self._create_lot(self.product_a)
|
96
|
+
# ensure we have our new package in the same location
|
97
|
+
self._update_qty_in_location(source_location, line.product_id, 8, lot=new_lot)
|
98
|
+
expected_message = self.msg_store.lot_replaced_by_lot(initial_lot, new_lot)
|
99
|
+
expected_message["body"] += " The quantity to do has changed!"
|
100
|
+
self.change_package_lot.change_lot(
|
101
|
+
line,
|
102
|
+
new_lot,
|
103
|
+
# success callback
|
104
|
+
lambda move_line, message=None: self.assertEqual(message, expected_message),
|
105
|
+
# failure callback
|
106
|
+
self.unreachable_func,
|
107
|
+
)
|
108
|
+
self.assertRecordValues(line, [{"lot_id": new_lot.id, "reserved_qty": 8}])
|
109
|
+
other_line = line.move_id.move_line_ids - line
|
110
|
+
self.assertRecordValues(
|
111
|
+
other_line, [{"lot_id": initial_lot.id, "reserved_qty": 2}]
|
112
|
+
)
|
113
|
+
# check that reservations have been updated
|
114
|
+
self.assert_quant_reserved_qty(line, lambda: 2, lot=initial_lot)
|
115
|
+
self.assert_quant_reserved_qty(line, lambda: line.reserved_qty, lot=new_lot)
|
116
|
+
|
117
|
+
def test_change_lot_zero_quant_error(self):
|
118
|
+
"""No quant in the location for the scanned lot
|
119
|
+
|
120
|
+
As the user scanned it, it's an inventory error.
|
121
|
+
"""
|
122
|
+
initial_lot = self._create_lot(self.product_a)
|
123
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=initial_lot)
|
124
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
125
|
+
picking.action_assign()
|
126
|
+
line = picking.move_line_ids
|
127
|
+
new_lot = self._create_lot(self.product_a)
|
128
|
+
expected_message = self.msg_store.cannot_change_lot_already_picked(new_lot)
|
129
|
+
self.change_package_lot.change_lot(
|
130
|
+
line,
|
131
|
+
new_lot,
|
132
|
+
# success callback
|
133
|
+
self.unreachable_func,
|
134
|
+
# failure callback
|
135
|
+
lambda move_line, message=None: self.assertEqual(message, expected_message),
|
136
|
+
)
|
137
|
+
|
138
|
+
self.assertRecordValues(line, [{"lot_id": initial_lot.id, "reserved_qty": 10}])
|
139
|
+
# check that reservations have not been updated
|
140
|
+
self.assert_quant_reserved_qty(line, lambda: line.reserved_qty, lot=initial_lot)
|
141
|
+
self.assert_quant_reserved_qty(line, lambda: 0, lot=new_lot)
|
142
|
+
|
143
|
+
def test_change_lot_package_explode_ok(self):
|
144
|
+
"""Scan a lot on units replacing a package"""
|
145
|
+
initial_lot = self._create_lot(self.product_a)
|
146
|
+
package = self._create_package_in_location(
|
147
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=initial_lot)]
|
148
|
+
)
|
149
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
150
|
+
picking.action_assign()
|
151
|
+
line = picking.move_line_ids
|
152
|
+
self.assertEqual(line.lot_id, initial_lot)
|
153
|
+
self.assertEqual(line.package_id, package)
|
154
|
+
|
155
|
+
new_lot = self._create_lot(self.product_a)
|
156
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=new_lot)
|
157
|
+
expected_message = self.msg_store.lot_replaced_by_lot(initial_lot, new_lot)
|
158
|
+
self.change_package_lot.change_lot(
|
159
|
+
line,
|
160
|
+
new_lot,
|
161
|
+
# success callback
|
162
|
+
lambda move_line, message=None: self.assertEqual(message, expected_message),
|
163
|
+
# failure callback
|
164
|
+
self.unreachable_func,
|
165
|
+
)
|
166
|
+
|
167
|
+
self.assertRecordValues(
|
168
|
+
line,
|
169
|
+
[
|
170
|
+
{
|
171
|
+
"lot_id": new_lot.id,
|
172
|
+
"reserved_qty": 10,
|
173
|
+
"package_id": False,
|
174
|
+
"package_level_id": False,
|
175
|
+
}
|
176
|
+
],
|
177
|
+
)
|
178
|
+
|
179
|
+
# check that reservations have been updated
|
180
|
+
self.assert_quant_reserved_qty(line, lambda: 0, lot=initial_lot)
|
181
|
+
self.assert_quant_reserved_qty(line, lambda: line.reserved_qty, lot=new_lot)
|
182
|
+
|
183
|
+
def test_change_lot_reserved_qty_ok(self):
|
184
|
+
"""Scan a lot already reserved by other lines
|
185
|
+
|
186
|
+
It should unreserve the other line, use the lot for the current line,
|
187
|
+
and re-reserve the other move.
|
188
|
+
"""
|
189
|
+
initial_lot = self._create_lot(self.product_a)
|
190
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=initial_lot)
|
191
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
192
|
+
picking.action_assign()
|
193
|
+
line = picking.move_line_ids
|
194
|
+
self.assertEqual(line.lot_id, initial_lot)
|
195
|
+
|
196
|
+
new_lot = self._create_lot(self.product_a)
|
197
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=new_lot)
|
198
|
+
picking2 = self._create_picking(lines=[(self.product_a, 10)])
|
199
|
+
picking2.action_assign()
|
200
|
+
line2 = picking2.move_line_ids
|
201
|
+
self.assertEqual(line2.lot_id, new_lot)
|
202
|
+
|
203
|
+
expected_message = self.msg_store.lot_replaced_by_lot(initial_lot, new_lot)
|
204
|
+
self.change_package_lot.change_lot(
|
205
|
+
line,
|
206
|
+
new_lot,
|
207
|
+
# success callback
|
208
|
+
lambda move_line, message=None: self.assertEqual(message, expected_message),
|
209
|
+
# failure callback
|
210
|
+
self.unreachable_func,
|
211
|
+
)
|
212
|
+
|
213
|
+
self.assertRecordValues(line, [{"lot_id": new_lot.id, "reserved_qty": 10}])
|
214
|
+
# line has been re-created
|
215
|
+
line2 = picking2.move_line_ids
|
216
|
+
self.assertRecordValues(line2, [{"lot_id": initial_lot.id, "reserved_qty": 10}])
|
217
|
+
|
218
|
+
# check that reservations have been updated
|
219
|
+
self.assert_quant_reserved_qty(line, lambda: line.reserved_qty, lot=new_lot)
|
220
|
+
self.assert_quant_reserved_qty(
|
221
|
+
line2, lambda: line2.reserved_qty, lot=initial_lot
|
222
|
+
)
|
223
|
+
|
224
|
+
def test_change_lot_reserved_partial_qty_ok(self):
|
225
|
+
"""Scan a lot already reserved by other lines and can only be reserved
|
226
|
+
partially
|
227
|
+
|
228
|
+
It should unreserve the other line, use the lot for the current line,
|
229
|
+
and re-reserve the other move. The quantity for the current line must
|
230
|
+
be adapted to the available
|
231
|
+
"""
|
232
|
+
initial_lot = self._create_lot(self.product_a)
|
233
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=initial_lot)
|
234
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
235
|
+
picking.action_assign()
|
236
|
+
line = picking.move_line_ids
|
237
|
+
self.assertEqual(line.lot_id, initial_lot)
|
238
|
+
|
239
|
+
new_lot = self._create_lot(self.product_a)
|
240
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 8, lot=new_lot)
|
241
|
+
picking2 = self._create_picking(lines=[(self.product_a, 8)])
|
242
|
+
picking2.action_assign()
|
243
|
+
line2 = picking2.move_line_ids
|
244
|
+
self.assertEqual(line2.lot_id, new_lot)
|
245
|
+
|
246
|
+
expected_message = self.msg_store.lot_replaced_by_lot(initial_lot, new_lot)
|
247
|
+
expected_message["body"] += " The quantity to do has changed!"
|
248
|
+
self.change_package_lot.change_lot(
|
249
|
+
line,
|
250
|
+
new_lot,
|
251
|
+
# success callback
|
252
|
+
lambda move_line, message=None: self.assertEqual(message, expected_message),
|
253
|
+
# failure callback
|
254
|
+
self.unreachable_func,
|
255
|
+
)
|
256
|
+
|
257
|
+
self.assertRecordValues(line, [{"lot_id": new_lot.id, "reserved_qty": 8}])
|
258
|
+
other_line = picking.move_line_ids - line
|
259
|
+
self.assertRecordValues(
|
260
|
+
other_line, [{"lot_id": initial_lot.id, "reserved_qty": 2}]
|
261
|
+
)
|
262
|
+
# line has been re-created
|
263
|
+
line2 = picking2.move_line_ids
|
264
|
+
self.assertRecordValues(line2, [{"lot_id": initial_lot.id, "reserved_qty": 8}])
|
265
|
+
|
266
|
+
# check that reservations have been updated
|
267
|
+
self.assert_quant_reserved_qty(line, lambda: line.reserved_qty, lot=new_lot)
|
268
|
+
# both line2 and the line for the 2 remaining will re-reserve the initial lot
|
269
|
+
self.assert_quant_reserved_qty(
|
270
|
+
other_line,
|
271
|
+
lambda: line2.reserved_qty + other_line.reserved_qty,
|
272
|
+
lot=initial_lot,
|
273
|
+
)
|
274
|
+
|
275
|
+
def test_change_lot_reserved_qty_done_error(self):
|
276
|
+
"""Scan a lot already reserved by other *picked* lines
|
277
|
+
|
278
|
+
Cannot "steal" lot from picked lines
|
279
|
+
"""
|
280
|
+
initial_lot = self._create_lot(self.product_a)
|
281
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=initial_lot)
|
282
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
283
|
+
picking.action_assign()
|
284
|
+
line = picking.move_line_ids
|
285
|
+
self.assertEqual(line.lot_id, initial_lot)
|
286
|
+
|
287
|
+
new_lot = self._create_lot(self.product_a)
|
288
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=new_lot)
|
289
|
+
picking2 = self._create_picking(lines=[(self.product_a, 10)])
|
290
|
+
picking2.action_assign()
|
291
|
+
line2 = picking2.move_line_ids
|
292
|
+
self.assertEqual(line2.lot_id, new_lot)
|
293
|
+
line2.qty_done = 10.0
|
294
|
+
|
295
|
+
expected_message = self.msg_store.cannot_change_lot_already_picked(new_lot)
|
296
|
+
self.change_package_lot.change_lot(
|
297
|
+
line,
|
298
|
+
new_lot,
|
299
|
+
# success callback
|
300
|
+
self.unreachable_func,
|
301
|
+
# failure callback
|
302
|
+
lambda move_line, message=None: self.assertEqual(message, expected_message),
|
303
|
+
)
|
304
|
+
|
305
|
+
# no changes
|
306
|
+
self.assertRecordValues(line, [{"lot_id": initial_lot.id, "reserved_qty": 10}])
|
307
|
+
self.assertRecordValues(
|
308
|
+
line2, [{"lot_id": new_lot.id, "reserved_qty": 10, "qty_done": 10.0}]
|
309
|
+
)
|
310
|
+
self.assert_quant_reserved_qty(line, lambda: line.reserved_qty, lot=initial_lot)
|
311
|
+
self.assert_quant_reserved_qty(line2, lambda: line2.reserved_qty, lot=new_lot)
|
312
|
+
|
313
|
+
def test_change_lot_different_location_error(self):
|
314
|
+
"If the scanned lot is in a different location, we cannot process it"
|
315
|
+
self.product_a.tracking = "lot"
|
316
|
+
initial_lot = self._create_lot(self.product_a)
|
317
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=initial_lot)
|
318
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
319
|
+
picking.action_assign()
|
320
|
+
line = picking.move_line_ids
|
321
|
+
new_lot = self._create_lot(self.product_a)
|
322
|
+
# ensure we have our new lot in a different location
|
323
|
+
self._update_qty_in_location(self.shelf2, line.product_id, 10, lot=new_lot)
|
324
|
+
expected_message = self.msg_store.cannot_change_lot_already_picked(new_lot)
|
325
|
+
self.change_package_lot.change_lot(
|
326
|
+
line,
|
327
|
+
new_lot,
|
328
|
+
# success callback
|
329
|
+
self.unreachable_func,
|
330
|
+
# failure callback
|
331
|
+
lambda move_line, message=None: self.assertEqual(message, expected_message),
|
332
|
+
)
|
333
|
+
|
334
|
+
self.assertRecordValues(line, [{"lot_id": initial_lot.id}])
|
335
|
+
# check that reservations have not been updated
|
336
|
+
self.assert_quant_reserved_qty(line, lambda: line.reserved_qty, lot=initial_lot)
|
337
|
+
self.assert_quant_reserved_qty(line, lambda: 0, lot=new_lot)
|
338
|
+
|
339
|
+
def test_change_lot_in_several_packages_error(self):
|
340
|
+
self.product_a.tracking = "lot"
|
341
|
+
initial_lot = self._create_lot(self.product_a)
|
342
|
+
self._create_package_in_location(
|
343
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=initial_lot)]
|
344
|
+
)
|
345
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
346
|
+
picking.action_assign()
|
347
|
+
line = picking.move_line_ids
|
348
|
+
# create 2 packages for the same new lot in the same location
|
349
|
+
new_lot = self._create_lot(self.product_a)
|
350
|
+
self._create_package_in_location(
|
351
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, new_lot)]
|
352
|
+
)
|
353
|
+
self._create_package_in_location(
|
354
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, new_lot)]
|
355
|
+
)
|
356
|
+
self.change_package_lot.change_lot(
|
357
|
+
line,
|
358
|
+
new_lot,
|
359
|
+
# success callback
|
360
|
+
self.unreachable_func,
|
361
|
+
# failure callback
|
362
|
+
lambda move_line, message=None: self.assertEqual(
|
363
|
+
message, self.msg_store.several_packs_in_location(self.shelf1)
|
364
|
+
),
|
365
|
+
)
|
366
|
+
|
367
|
+
def test_change_lot_in_package_ok(self):
|
368
|
+
self.product_a.tracking = "lot"
|
369
|
+
initial_lot = self._create_lot(self.product_a)
|
370
|
+
initial_package = self._create_package_in_location(
|
371
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=initial_lot)]
|
372
|
+
)
|
373
|
+
# ensure we have our new package in the same location
|
374
|
+
new_lot = self._create_lot(self.product_a)
|
375
|
+
new_package = self._create_package_in_location(
|
376
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=new_lot)]
|
377
|
+
)
|
378
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
379
|
+
picking.action_assign()
|
380
|
+
line = picking.move_line_ids
|
381
|
+
self.change_package_lot.change_lot(
|
382
|
+
line,
|
383
|
+
new_lot,
|
384
|
+
# success callback
|
385
|
+
lambda move_line, message=None: self.assertEqual(
|
386
|
+
message,
|
387
|
+
self.msg_store.package_replaced_by_package(
|
388
|
+
initial_package, new_package
|
389
|
+
),
|
390
|
+
),
|
391
|
+
# failure callback
|
392
|
+
self.unreachable_func,
|
393
|
+
)
|
394
|
+
self.assertRecordValues(
|
395
|
+
line,
|
396
|
+
[
|
397
|
+
{
|
398
|
+
"package_id": new_package.id,
|
399
|
+
"result_package_id": new_package.id,
|
400
|
+
"lot_id": new_lot.id,
|
401
|
+
"reserved_qty": 10.0,
|
402
|
+
}
|
403
|
+
],
|
404
|
+
)
|
405
|
+
self.assertRecordValues(line.package_level_id, [{"package_id": new_package.id}])
|
406
|
+
# check that reservations have been updated
|
407
|
+
self.assert_quant_reserved_qty(line, lambda: 0, package=initial_package)
|
408
|
+
self.assert_quant_reserved_qty(
|
409
|
+
line, lambda: line.reserved_qty, package=new_package
|
410
|
+
)
|
411
|
+
|
412
|
+
def test_change_lot_in_package_no_initial_package_ok(self):
|
413
|
+
self.product_a.tracking = "lot"
|
414
|
+
initial_lot = self._create_lot(self.product_a)
|
415
|
+
self._update_qty_in_location(self.shelf1, self.product_a, 10, lot=initial_lot)
|
416
|
+
# ensure we have our new package in the same location
|
417
|
+
new_lot = self._create_lot(self.product_a)
|
418
|
+
new_package = self._create_package_in_location(
|
419
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=new_lot)]
|
420
|
+
)
|
421
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
422
|
+
picking.action_assign()
|
423
|
+
line = picking.move_line_ids
|
424
|
+
self.change_package_lot.change_lot(
|
425
|
+
line,
|
426
|
+
new_lot,
|
427
|
+
# success callback
|
428
|
+
lambda move_line, message=None: self.assertEqual(
|
429
|
+
message, self.msg_store.units_replaced_by_package(new_package)
|
430
|
+
),
|
431
|
+
# failure callback
|
432
|
+
self.unreachable_func,
|
433
|
+
)
|
434
|
+
self.assertRecordValues(
|
435
|
+
line,
|
436
|
+
[
|
437
|
+
{
|
438
|
+
"package_id": new_package.id,
|
439
|
+
"result_package_id": new_package.id,
|
440
|
+
"lot_id": new_lot.id,
|
441
|
+
"reserved_qty": 10.0,
|
442
|
+
}
|
443
|
+
],
|
444
|
+
)
|
445
|
+
self.assertRecordValues(line.package_level_id, [{"package_id": new_package.id}])
|
446
|
+
# check that reservations have been updated
|
447
|
+
self.assert_quant_reserved_qty(line, lambda: 0, lot=initial_lot)
|
448
|
+
self.assert_quant_reserved_qty(
|
449
|
+
line, lambda: line.reserved_qty, package=new_package
|
450
|
+
)
|
451
|
+
|
452
|
+
def test_change_pack_different_content_error(self):
|
453
|
+
# create the initial package, that will be reserved first
|
454
|
+
initial_package = self._create_package_in_location(
|
455
|
+
self.shelf1,
|
456
|
+
[
|
457
|
+
self.PackageContent(self.product_a, 10, lot=None),
|
458
|
+
self.PackageContent(self.product_b, 10, lot=None),
|
459
|
+
],
|
460
|
+
)
|
461
|
+
picking = self._create_picking_with_package_level(initial_package)
|
462
|
+
# create a new package in the same location
|
463
|
+
# with a different content
|
464
|
+
new_package = self._create_package_in_location(
|
465
|
+
self.shelf1, [self.PackageContent(self.product_b, 8, lot=None)]
|
466
|
+
)
|
467
|
+
|
468
|
+
lines = picking.move_line_ids
|
469
|
+
# try to use the new package, which doesn't contain our product,
|
470
|
+
# cannot be changed
|
471
|
+
self.change_package_lot.change_package(
|
472
|
+
lines[0],
|
473
|
+
new_package,
|
474
|
+
# success callback
|
475
|
+
self.unreachable_func,
|
476
|
+
# failure callback
|
477
|
+
lambda move_line, message=None: self.assertEqual(
|
478
|
+
message, self.msg_store.package_different_content(new_package)
|
479
|
+
),
|
480
|
+
)
|
481
|
+
|
482
|
+
def test_change_pack_multi_content_with_lot(self):
|
483
|
+
"""Switch package for a line which was part of a multi-products package
|
484
|
+
|
485
|
+
We have a move line which is part of a package with more than one
|
486
|
+
product and the other product is moved by another move line.
|
487
|
+
|
488
|
+
We want to pick the goods for product A in a different package. What
|
489
|
+
should happen is:
|
490
|
+
|
491
|
+
* the package level is exploded, as we will no longer move the entire
|
492
|
+
package
|
493
|
+
* the move line for product A should now use the new package, and be
|
494
|
+
updated with the lot of the package
|
495
|
+
* the move line for the other product should keep the other package, if
|
496
|
+
the user want to change the package for the other product too, they
|
497
|
+
can do it when they pick it
|
498
|
+
"""
|
499
|
+
(self.product_a + self.product_b).tracking = "lot"
|
500
|
+
# create a package with 2 products tracked by lot, stored in shelf1
|
501
|
+
# this package is reserved first on the move line
|
502
|
+
initial_lot_a = self._create_lot(self.product_a)
|
503
|
+
initial_lot_b = self._create_lot(self.product_b)
|
504
|
+
initial_package = self._create_package_in_location(
|
505
|
+
self.shelf1,
|
506
|
+
[
|
507
|
+
self.PackageContent(self.product_a, 10, initial_lot_a),
|
508
|
+
self.PackageContent(self.product_b, 10, initial_lot_b),
|
509
|
+
],
|
510
|
+
)
|
511
|
+
|
512
|
+
# create and reserve our transfer using the initial package
|
513
|
+
picking = self._create_picking_with_package_level(initial_package)
|
514
|
+
|
515
|
+
lines = picking.move_line_ids
|
516
|
+
|
517
|
+
# create a second package with the same content, which will be used
|
518
|
+
# as replacement
|
519
|
+
new_lot_a = self._create_lot(self.product_a)
|
520
|
+
new_lot_b = self._create_lot(self.product_b)
|
521
|
+
new_package = self._create_package_in_location(
|
522
|
+
self.shelf1,
|
523
|
+
[
|
524
|
+
self.PackageContent(self.product_a, 10, new_lot_a),
|
525
|
+
self.PackageContent(self.product_b, 10, new_lot_b),
|
526
|
+
],
|
527
|
+
)
|
528
|
+
line1, line2 = lines
|
529
|
+
self.change_package_lot.change_package(
|
530
|
+
line1,
|
531
|
+
new_package,
|
532
|
+
# success callback
|
533
|
+
lambda move_line, message=None: self.assertEqual(
|
534
|
+
message,
|
535
|
+
self.msg_store.package_replaced_by_package(
|
536
|
+
initial_package, new_package
|
537
|
+
),
|
538
|
+
),
|
539
|
+
# failure callback
|
540
|
+
self.unreachable_func,
|
541
|
+
)
|
542
|
+
self.assertRecordValues(
|
543
|
+
line1,
|
544
|
+
[
|
545
|
+
{
|
546
|
+
"package_id": new_package.id,
|
547
|
+
# we are no longer moving an entire package
|
548
|
+
"result_package_id": False,
|
549
|
+
"lot_id": new_lot_a.id,
|
550
|
+
"reserved_qty": 10.0,
|
551
|
+
}
|
552
|
+
],
|
553
|
+
)
|
554
|
+
self.assertRecordValues(
|
555
|
+
line2,
|
556
|
+
[
|
557
|
+
{
|
558
|
+
"package_id": initial_package.id,
|
559
|
+
# we are no longer moving an entire package
|
560
|
+
"result_package_id": False,
|
561
|
+
"lot_id": initial_lot_b.id,
|
562
|
+
"reserved_qty": 10.0,
|
563
|
+
}
|
564
|
+
],
|
565
|
+
)
|
566
|
+
# check that reservations have been updated
|
567
|
+
self.assert_quant_reserved_qty(line1, lambda: 0, package=initial_package)
|
568
|
+
self.assert_quant_reserved_qty(
|
569
|
+
line2, lambda: line2.reserved_qty, package=initial_package
|
570
|
+
)
|
571
|
+
self.assert_quant_reserved_qty(
|
572
|
+
line1, lambda: line1.reserved_qty, package=new_package
|
573
|
+
)
|
574
|
+
self.assert_quant_reserved_qty(line2, lambda: 0, package=new_package)
|
575
|
+
|
576
|
+
def test_change_pack_different_location(self):
|
577
|
+
initial_package = self._create_package_in_location(
|
578
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
|
579
|
+
)
|
580
|
+
# put a package in shelf2 in the system, but we assume that in real,
|
581
|
+
# the operator put it in shelf1
|
582
|
+
new_package = self._create_package_in_location(
|
583
|
+
self.shelf2, [self.PackageContent(self.product_a, 10, lot=None)]
|
584
|
+
)
|
585
|
+
|
586
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
587
|
+
picking.action_assign()
|
588
|
+
line = picking.move_line_ids
|
589
|
+
self.assertEqual(line.package_id, initial_package)
|
590
|
+
# when the operator wants to pick the initial package, in shelf1, the new
|
591
|
+
# package is in front of the other so they want to change the package
|
592
|
+
self.change_package_lot.change_package(
|
593
|
+
line,
|
594
|
+
new_package,
|
595
|
+
# success callback
|
596
|
+
lambda move_line, message=None: self.assertEqual(
|
597
|
+
message,
|
598
|
+
self.msg_store.package_replaced_by_package(
|
599
|
+
initial_package, new_package
|
600
|
+
),
|
601
|
+
),
|
602
|
+
# failure callback
|
603
|
+
self.unreachable_func,
|
604
|
+
)
|
605
|
+
|
606
|
+
self.assertRecordValues(
|
607
|
+
line, [{"package_id": new_package.id, "result_package_id": new_package.id}]
|
608
|
+
)
|
609
|
+
self.assertRecordValues(line.package_level_id, [{"package_id": new_package.id}])
|
610
|
+
# check that reservations have been updated, the new package is not
|
611
|
+
# supposed to be in shelf2 anymore, and we should have no reserved qty
|
612
|
+
# for the initial package anymore
|
613
|
+
self.assert_quant_package_qty(self.shelf2, new_package, lambda: 0)
|
614
|
+
self.assert_quant_reserved_qty(line, lambda: 0, package=initial_package)
|
615
|
+
self.assert_quant_reserved_qty(
|
616
|
+
line, lambda: line.reserved_qty, package=new_package
|
617
|
+
)
|
618
|
+
|
619
|
+
def test_change_pack_different_location_reserved_package(self):
|
620
|
+
initial_package = self._create_package_in_location(
|
621
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
|
622
|
+
)
|
623
|
+
|
624
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
625
|
+
picking.action_assign()
|
626
|
+
line = picking.move_line_ids
|
627
|
+
self.assertEqual(line.package_id, initial_package)
|
628
|
+
|
629
|
+
# put a package in shelf2 in the system, but we assume that in real,
|
630
|
+
# the operator put it in shelf1
|
631
|
+
new_package = self._create_package_in_location(
|
632
|
+
self.shelf2, [self.PackageContent(self.product_a, 10, lot=None)]
|
633
|
+
)
|
634
|
+
picking2 = self._create_picking(lines=[(self.product_a, 10)])
|
635
|
+
picking2.action_assign()
|
636
|
+
line2 = picking2.move_line_ids
|
637
|
+
self.assertEqual(line2.package_id, new_package)
|
638
|
+
|
639
|
+
# When the operator wants to pick the initial package, in shelf1, the new
|
640
|
+
# package is in front of the other so they want to change the package.
|
641
|
+
# The new package was supposed to be in shelf2 but is in fact in
|
642
|
+
# shelf1.
|
643
|
+
# An inventory must move it in shelf1 before we change the package on the line.
|
644
|
+
# Line2 must be unreserved and reserved again.
|
645
|
+
self.change_package_lot.change_package(
|
646
|
+
line,
|
647
|
+
new_package,
|
648
|
+
# success callback
|
649
|
+
lambda move_line, message=None: self.assertEqual(
|
650
|
+
message,
|
651
|
+
self.msg_store.package_replaced_by_package(
|
652
|
+
initial_package, new_package
|
653
|
+
),
|
654
|
+
),
|
655
|
+
# failure callback
|
656
|
+
self.unreachable_func,
|
657
|
+
)
|
658
|
+
|
659
|
+
# line2 has been re-created
|
660
|
+
line2 = picking2.move_line_ids
|
661
|
+
self.assertRecordValues(
|
662
|
+
line + line2,
|
663
|
+
[
|
664
|
+
{
|
665
|
+
"package_id": new_package.id,
|
666
|
+
"result_package_id": new_package.id,
|
667
|
+
"location_id": self.shelf1.id,
|
668
|
+
"reserved_qty": 10.0,
|
669
|
+
},
|
670
|
+
{
|
671
|
+
"package_id": initial_package.id,
|
672
|
+
"result_package_id": initial_package.id,
|
673
|
+
"location_id": self.shelf1.id,
|
674
|
+
"reserved_qty": 10.0,
|
675
|
+
},
|
676
|
+
],
|
677
|
+
)
|
678
|
+
self.assertRecordValues(line.package_level_id, [{"package_id": new_package.id}])
|
679
|
+
self.assertRecordValues(
|
680
|
+
line2.package_level_id, [{"package_id": initial_package.id}]
|
681
|
+
)
|
682
|
+
# check that reservations have been updated, the new package is not
|
683
|
+
# supposed to be in shelf2 anymore, and we should have no reserved qty
|
684
|
+
# for the initial package anymore
|
685
|
+
self.assert_quant_package_qty(self.shelf2, new_package, lambda: 0)
|
686
|
+
self.assert_quant_reserved_qty(
|
687
|
+
line, lambda: line.reserved_qty, package=new_package
|
688
|
+
)
|
689
|
+
self.assert_quant_reserved_qty(
|
690
|
+
line2, lambda: line2.reserved_qty, package=initial_package
|
691
|
+
)
|
692
|
+
|
693
|
+
def test_change_pack_different_location_reserved_package_qty_done(self):
|
694
|
+
initial_package = self._create_package_in_location(
|
695
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
|
696
|
+
)
|
697
|
+
|
698
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
699
|
+
picking.action_assign()
|
700
|
+
line = picking.move_line_ids
|
701
|
+
self.assertEqual(line.package_id, initial_package)
|
702
|
+
|
703
|
+
# put a package in shelf2 in the system, but we assume that in real,
|
704
|
+
# the operator put it in shelf1
|
705
|
+
new_package = self._create_package_in_location(
|
706
|
+
self.shelf2, [self.PackageContent(self.product_a, 10, lot=None)]
|
707
|
+
)
|
708
|
+
picking2 = self._create_picking(lines=[(self.product_a, 10)])
|
709
|
+
picking2.action_assign()
|
710
|
+
line2 = picking2.move_line_ids
|
711
|
+
self.assertEqual(line2.package_id, new_package)
|
712
|
+
line2.qty_done = 10.0
|
713
|
+
|
714
|
+
# The new package was supposed to be in shelf2 but is in fact in shelf1.
|
715
|
+
# The package has already been picked in shelf2 (unlikely to happen...
|
716
|
+
# still we have to handle it). Forbid to pick.
|
717
|
+
expected_message = self.msg_store.package_change_error(
|
718
|
+
new_package,
|
719
|
+
"Package {} has been partially picked in another location".format(
|
720
|
+
new_package.display_name
|
721
|
+
),
|
722
|
+
)
|
723
|
+
self.change_package_lot.change_package(
|
724
|
+
line,
|
725
|
+
new_package,
|
726
|
+
# success callback
|
727
|
+
self.unreachable_func,
|
728
|
+
# failure callback
|
729
|
+
lambda move_line, message=None: self.assertEqual(message, expected_message),
|
730
|
+
)
|
731
|
+
|
732
|
+
# line2 has been re-created
|
733
|
+
line2 = picking2.move_line_ids
|
734
|
+
self.assertRecordValues(
|
735
|
+
line + line2,
|
736
|
+
[
|
737
|
+
{
|
738
|
+
"package_id": initial_package.id,
|
739
|
+
"result_package_id": initial_package.id,
|
740
|
+
"location_id": self.shelf1.id,
|
741
|
+
"reserved_qty": 10.0,
|
742
|
+
},
|
743
|
+
{
|
744
|
+
"package_id": new_package.id,
|
745
|
+
"result_package_id": new_package.id,
|
746
|
+
"location_id": self.shelf2.id,
|
747
|
+
"reserved_qty": 10.0,
|
748
|
+
},
|
749
|
+
],
|
750
|
+
)
|
751
|
+
# no change
|
752
|
+
self.assertRecordValues(
|
753
|
+
line.package_level_id, [{"package_id": initial_package.id}]
|
754
|
+
)
|
755
|
+
self.assertRecordValues(
|
756
|
+
line2.package_level_id, [{"package_id": new_package.id}]
|
757
|
+
)
|
758
|
+
self.assert_quant_package_qty(self.shelf2, new_package, lambda: 10.0)
|
759
|
+
self.assert_quant_reserved_qty(
|
760
|
+
line, lambda: line.reserved_qty, package=initial_package
|
761
|
+
)
|
762
|
+
self.assert_quant_reserved_qty(
|
763
|
+
line2, lambda: line2.reserved_qty, package=new_package
|
764
|
+
)
|
765
|
+
|
766
|
+
def test_change_pack_lot_change_pack_less_qty_ok(self):
|
767
|
+
initial_package = self._create_package_in_location(
|
768
|
+
self.shelf1, [self.PackageContent(self.product_a, 100, lot=None)]
|
769
|
+
)
|
770
|
+
|
771
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
772
|
+
picking.action_assign()
|
773
|
+
line = picking.move_line_ids
|
774
|
+
|
775
|
+
self.assertRecordValues(
|
776
|
+
line,
|
777
|
+
[
|
778
|
+
{
|
779
|
+
"package_id": initial_package.id,
|
780
|
+
# since we don't move the entire package (10 out of 100), no
|
781
|
+
# result package
|
782
|
+
"result_package_id": False,
|
783
|
+
"reserved_qty": 10.0,
|
784
|
+
}
|
785
|
+
],
|
786
|
+
)
|
787
|
+
self.assertFalse(line.package_level_id)
|
788
|
+
|
789
|
+
# ensure we have our new package in the same location
|
790
|
+
new_package = self._create_package_in_location(
|
791
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
|
792
|
+
)
|
793
|
+
self.change_package_lot.change_package(
|
794
|
+
line,
|
795
|
+
new_package,
|
796
|
+
# success callback
|
797
|
+
lambda move_line, message=None: self.assertEqual(
|
798
|
+
message,
|
799
|
+
self.msg_store.package_replaced_by_package(
|
800
|
+
initial_package, new_package
|
801
|
+
),
|
802
|
+
),
|
803
|
+
# failure callback
|
804
|
+
self.unreachable_func,
|
805
|
+
)
|
806
|
+
self.assertRecordValues(
|
807
|
+
line,
|
808
|
+
[
|
809
|
+
{
|
810
|
+
"package_id": new_package.id,
|
811
|
+
"result_package_id": new_package.id,
|
812
|
+
"reserved_qty": 10.0,
|
813
|
+
}
|
814
|
+
],
|
815
|
+
)
|
816
|
+
self.assertRecordValues(line.package_level_id, [{"package_id": new_package.id}])
|
817
|
+
|
818
|
+
# check that reservations have been updated
|
819
|
+
self.assert_quant_reserved_qty(line, lambda: 0, package=initial_package)
|
820
|
+
self.assert_quant_reserved_qty(
|
821
|
+
line, lambda: line.reserved_qty, package=new_package
|
822
|
+
)
|
823
|
+
|
824
|
+
def test_change_pack_steal_from_other_move_line(self):
|
825
|
+
"""Exchange pack with another line
|
826
|
+
|
827
|
+
When we scan the package used on another line not picked yet (qty_done
|
828
|
+
== 0), we unreserve the other line and use its package. The other line
|
829
|
+
is reserved again and should reserve the package used initially on our
|
830
|
+
move line.
|
831
|
+
"""
|
832
|
+
# create 2 picking, each with its own package
|
833
|
+
package1 = self._create_package_in_location(
|
834
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
|
835
|
+
)
|
836
|
+
picking1 = self._create_picking_with_package_level(package1)
|
837
|
+
self.assertEqual(picking1.move_line_ids.package_id, package1)
|
838
|
+
|
839
|
+
package2 = self._create_package_in_location(
|
840
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
|
841
|
+
)
|
842
|
+
picking2 = self._create_picking_with_package_level(package2)
|
843
|
+
self.assertEqual(picking2.move_line_ids.package_id, package2)
|
844
|
+
|
845
|
+
line = picking1.move_line_ids
|
846
|
+
|
847
|
+
# We "steal" package2 for the picking1
|
848
|
+
self.change_package_lot.change_package(
|
849
|
+
line,
|
850
|
+
package2,
|
851
|
+
# success callback
|
852
|
+
lambda move_line, message=None: self.assertEqual(
|
853
|
+
message, self.msg_store.package_replaced_by_package(package1, package2)
|
854
|
+
),
|
855
|
+
# failure callback
|
856
|
+
self.unreachable_func,
|
857
|
+
)
|
858
|
+
|
859
|
+
self.assertRecordValues(
|
860
|
+
picking1.move_line_ids,
|
861
|
+
[
|
862
|
+
{
|
863
|
+
"package_id": package2.id,
|
864
|
+
"result_package_id": package2.id,
|
865
|
+
"state": "assigned",
|
866
|
+
"reserved_qty": 10.0,
|
867
|
+
}
|
868
|
+
],
|
869
|
+
)
|
870
|
+
self.assertRecordValues(
|
871
|
+
picking2.move_line_ids,
|
872
|
+
[
|
873
|
+
{
|
874
|
+
"package_id": package1.id,
|
875
|
+
"result_package_id": package1.id,
|
876
|
+
"state": "assigned",
|
877
|
+
"reserved_qty": 10.0,
|
878
|
+
}
|
879
|
+
],
|
880
|
+
)
|
881
|
+
self.assertRecordValues(
|
882
|
+
picking1.package_level_ids,
|
883
|
+
[{"package_id": package2.id, "state": "assigned"}],
|
884
|
+
)
|
885
|
+
self.assertRecordValues(
|
886
|
+
picking2.package_level_ids,
|
887
|
+
[{"package_id": package1.id, "state": "assigned"}],
|
888
|
+
)
|
889
|
+
# check that reservations have been updated
|
890
|
+
self.assert_quant_reserved_qty(
|
891
|
+
picking1.move_line_ids,
|
892
|
+
lambda: picking1.move_line_ids.reserved_qty,
|
893
|
+
package=package2,
|
894
|
+
)
|
895
|
+
self.assert_quant_reserved_qty(
|
896
|
+
picking2.move_line_ids,
|
897
|
+
lambda: picking2.move_line_ids.reserved_qty,
|
898
|
+
package=package1,
|
899
|
+
)
|
900
|
+
|
901
|
+
def test_other_line_with_qty_done(self):
|
902
|
+
"""Try to exchange pack with other line with qty_done
|
903
|
+
|
904
|
+
When we scan the package used on another line which has been picked
|
905
|
+
(qty_done > 0), do not unreserve the other line.
|
906
|
+
"""
|
907
|
+
# create 2 picking, each with its own package
|
908
|
+
package1 = self._create_package_in_location(
|
909
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
|
910
|
+
)
|
911
|
+
picking1 = self._create_picking_with_package_level(package1)
|
912
|
+
self.assertEqual(picking1.move_line_ids.package_id, package1)
|
913
|
+
|
914
|
+
package2 = self._create_package_in_location(
|
915
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
|
916
|
+
)
|
917
|
+
picking2 = self._create_picking_with_package_level(package2)
|
918
|
+
self.assertEqual(picking2.move_line_ids.package_id, package2)
|
919
|
+
|
920
|
+
line1 = picking1.move_line_ids
|
921
|
+
line2 = picking2.move_line_ids
|
922
|
+
line2.qty_done = 10
|
923
|
+
|
924
|
+
self.change_package_lot.change_package(
|
925
|
+
line1,
|
926
|
+
package2,
|
927
|
+
# success callback
|
928
|
+
self.unreachable_func,
|
929
|
+
# failure callback
|
930
|
+
lambda move_line, message=None: self.assertEqual(
|
931
|
+
message,
|
932
|
+
self.msg_store.package_change_error(
|
933
|
+
package2,
|
934
|
+
"Package {} does not contain available product {},"
|
935
|
+
" cannot replace package.".format(
|
936
|
+
package2.display_name, line1.product_id.display_name
|
937
|
+
),
|
938
|
+
),
|
939
|
+
),
|
940
|
+
)
|
941
|
+
|
942
|
+
# did not change
|
943
|
+
self.assertRecordValues(
|
944
|
+
picking1.move_line_ids,
|
945
|
+
[
|
946
|
+
{
|
947
|
+
"package_id": package1.id,
|
948
|
+
"result_package_id": package1.id,
|
949
|
+
"state": "assigned",
|
950
|
+
}
|
951
|
+
],
|
952
|
+
)
|
953
|
+
self.assertRecordValues(
|
954
|
+
picking2.move_line_ids,
|
955
|
+
[
|
956
|
+
{
|
957
|
+
"package_id": package2.id,
|
958
|
+
"result_package_id": package2.id,
|
959
|
+
"state": "assigned",
|
960
|
+
}
|
961
|
+
],
|
962
|
+
)
|
963
|
+
self.assertRecordValues(
|
964
|
+
picking1.package_level_ids,
|
965
|
+
[{"package_id": package1.id, "state": "assigned"}],
|
966
|
+
)
|
967
|
+
self.assertRecordValues(
|
968
|
+
picking2.package_level_ids,
|
969
|
+
[{"package_id": package2.id, "state": "assigned"}],
|
970
|
+
)
|
971
|
+
# check that reservations have been updated
|
972
|
+
self.assert_quant_reserved_qty(
|
973
|
+
picking1.move_line_ids,
|
974
|
+
lambda: picking1.move_line_ids.reserved_qty,
|
975
|
+
package=package1,
|
976
|
+
)
|
977
|
+
self.assert_quant_reserved_qty(
|
978
|
+
picking2.move_line_ids,
|
979
|
+
lambda: picking2.move_line_ids.reserved_qty,
|
980
|
+
package=package2,
|
981
|
+
)
|
982
|
+
|
983
|
+
def test_package_partial(self):
|
984
|
+
"""Try to exchange pack with a package partially picked
|
985
|
+
|
986
|
+
When we scan the package used on another line which has been picked
|
987
|
+
(qty_done > 0), but the new package still has unreserved quantity:
|
988
|
+
|
989
|
+
* the current line is updated for the remaining unreserved quantity
|
990
|
+
* a new line is created for the remaining
|
991
|
+
* the other already picked line is untouched
|
992
|
+
"""
|
993
|
+
# create 2 picking, each with its own package
|
994
|
+
package1 = self._create_package_in_location(
|
995
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
|
996
|
+
)
|
997
|
+
picking1 = self._create_picking_with_package_level(package1)
|
998
|
+
line1 = picking1.move_line_ids
|
999
|
+
self.assertEqual(line1.package_id, package1)
|
1000
|
+
|
1001
|
+
package2 = self._create_package_in_location(
|
1002
|
+
self.shelf1, [self.PackageContent(self.product_a, 10, lot=None)]
|
1003
|
+
)
|
1004
|
+
|
1005
|
+
# take partially in package2 (no package level as moving partial
|
1006
|
+
# package)
|
1007
|
+
picking2 = self._create_picking(lines=[(self.product_a, 8)])
|
1008
|
+
picking2.action_assign()
|
1009
|
+
line2 = picking2.move_line_ids
|
1010
|
+
self.assertEqual(line2.package_id, package2)
|
1011
|
+
|
1012
|
+
# this line is picked, should not be changed, but we still have
|
1013
|
+
# 2 units in package2
|
1014
|
+
line2.qty_done = line2.reserved_qty
|
1015
|
+
|
1016
|
+
self.change_package_lot.change_package(
|
1017
|
+
line1,
|
1018
|
+
package2,
|
1019
|
+
# success callback
|
1020
|
+
lambda move_line, message=None: self.assertEqual(
|
1021
|
+
message, self.msg_store.package_replaced_by_package(package1, package2)
|
1022
|
+
),
|
1023
|
+
# failure callback
|
1024
|
+
self.unreachable_func,
|
1025
|
+
)
|
1026
|
+
|
1027
|
+
self.assertRecordValues(
|
1028
|
+
line1,
|
1029
|
+
[
|
1030
|
+
{
|
1031
|
+
"package_id": package2.id,
|
1032
|
+
# not moved entirely by this transfer
|
1033
|
+
"result_package_id": False,
|
1034
|
+
"state": "assigned",
|
1035
|
+
# as the remaining was 2 units, the line is
|
1036
|
+
# changed to take only 2
|
1037
|
+
"reserved_qty": 2.0,
|
1038
|
+
}
|
1039
|
+
],
|
1040
|
+
)
|
1041
|
+
self.assertRecordValues(
|
1042
|
+
# this line should be unchanged
|
1043
|
+
line2,
|
1044
|
+
[
|
1045
|
+
{
|
1046
|
+
"package_id": package2.id,
|
1047
|
+
# not moved entirely by this transfer
|
1048
|
+
"result_package_id": False,
|
1049
|
+
"state": "assigned",
|
1050
|
+
"reserved_qty": 8.0,
|
1051
|
+
}
|
1052
|
+
],
|
1053
|
+
)
|
1054
|
+
|
1055
|
+
# A new line has been created for the quantity the line1
|
1056
|
+
# couldn't take in package2. It will take the first goods
|
1057
|
+
# available, which happen to be package1 (which was unreserved
|
1058
|
+
# when we changed the package of line1).
|
1059
|
+
remaining_line = picking1.move_line_ids - line1
|
1060
|
+
self.assertRecordValues(
|
1061
|
+
remaining_line,
|
1062
|
+
[
|
1063
|
+
{
|
1064
|
+
"package_id": package1.id,
|
1065
|
+
# not moved entirely by this transfer
|
1066
|
+
"result_package_id": False,
|
1067
|
+
"state": "assigned",
|
1068
|
+
# remaining qty for the 1st move
|
1069
|
+
"reserved_qty": 8.0,
|
1070
|
+
}
|
1071
|
+
],
|
1072
|
+
)
|
1073
|
+
|
1074
|
+
# the package1 must have only 8 reserved, for the remaining
|
1075
|
+
# of the line
|
1076
|
+
self.assertEqual(package1.quant_ids.reserved_quantity, 8)
|
1077
|
+
self.assertEqual(package2.quant_ids.reserved_quantity, 10)
|
1078
|
+
|
1079
|
+
# no package is moved entirely at once
|
1080
|
+
self.assertFalse(picking1.package_level_ids)
|
1081
|
+
self.assertFalse(picking2.package_level_ids)
|
1082
|
+
|
1083
|
+
def test_package_2_lines_1_move(self):
|
1084
|
+
"""Keep picked move line if we have 2 lines on a move
|
1085
|
+
|
1086
|
+
Create a situation where we have 2 move lines on a move, with different
|
1087
|
+
packages, 1 one of them is already picked (qty_done > 0), we change the
|
1088
|
+
package on the second one: the first one must not be changed.
|
1089
|
+
"""
|
1090
|
+
package1 = self._create_package_in_location(
|
1091
|
+
self.shelf1, [self.PackageContent(self.product_a, 4, lot=None)]
|
1092
|
+
)
|
1093
|
+
package2 = self._create_package_in_location(
|
1094
|
+
self.shelf1, [self.PackageContent(self.product_a, 8, lot=None)]
|
1095
|
+
)
|
1096
|
+
|
1097
|
+
# take partially in package2 (no package level as moving partial
|
1098
|
+
# package)
|
1099
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
1100
|
+
picking.action_assign()
|
1101
|
+
move = picking.move_ids
|
1102
|
+
line1, line2 = move.move_line_ids
|
1103
|
+
self.assertEqual(line1.package_id, package1)
|
1104
|
+
self.assertEqual(line2.package_id, package2)
|
1105
|
+
|
1106
|
+
# package to switch to
|
1107
|
+
package3 = self._create_package_in_location(
|
1108
|
+
self.shelf1, [self.PackageContent(self.product_a, 8, lot=None)]
|
1109
|
+
)
|
1110
|
+
|
1111
|
+
# this line is picked and must not be changed
|
1112
|
+
line1.qty_done = line1.reserved_qty
|
1113
|
+
|
1114
|
+
# as we change for package2, the line should get only the remaining
|
1115
|
+
# part of the package
|
1116
|
+
|
1117
|
+
self.change_package_lot.change_package(
|
1118
|
+
line2,
|
1119
|
+
package3,
|
1120
|
+
# success callback
|
1121
|
+
lambda move_line, message=None: self.assertEqual(
|
1122
|
+
message, self.msg_store.package_replaced_by_package(package2, package3)
|
1123
|
+
),
|
1124
|
+
# failure callback
|
1125
|
+
self.unreachable_func,
|
1126
|
+
)
|
1127
|
+
|
1128
|
+
self.assertRecordValues(
|
1129
|
+
line1 | line2,
|
1130
|
+
[
|
1131
|
+
{
|
1132
|
+
"package_id": package1.id,
|
1133
|
+
"state": "assigned",
|
1134
|
+
"reserved_qty": 4.0,
|
1135
|
+
"qty_done": 4.0,
|
1136
|
+
},
|
1137
|
+
{
|
1138
|
+
"package_id": package3.id,
|
1139
|
+
"state": "assigned",
|
1140
|
+
"reserved_qty": 6.0,
|
1141
|
+
"qty_done": 0.0,
|
1142
|
+
},
|
1143
|
+
],
|
1144
|
+
)
|
1145
|
+
|
1146
|
+
# package1 is moved entirely
|
1147
|
+
self.assertTrue(line1.package_level_id)
|
1148
|
+
# package2 is not moved entirely
|
1149
|
+
self.assertFalse(line2.package_level_id)
|
1150
|
+
|
1151
|
+
# the package1 must have only 8 reserved, for the remaining
|
1152
|
+
# of the line
|
1153
|
+
self.assertEqual(package1.quant_ids.reserved_quantity, 4)
|
1154
|
+
self.assertEqual(package2.quant_ids.reserved_quantity, 0)
|
1155
|
+
self.assertEqual(package3.quant_ids.reserved_quantity, 6)
|
1156
|
+
|
1157
|
+
def test_change_pack_same(self):
|
1158
|
+
initial_package = self._create_package_in_location(
|
1159
|
+
self.shelf1, [self.PackageContent(self.product_a, 100, lot=None)]
|
1160
|
+
)
|
1161
|
+
picking = self._create_picking(lines=[(self.product_a, 10)])
|
1162
|
+
picking.action_assign()
|
1163
|
+
line = picking.move_line_ids
|
1164
|
+
self.assertEqual(line.package_id, initial_package)
|
1165
|
+
self.change_package_lot.change_package(
|
1166
|
+
line,
|
1167
|
+
initial_package,
|
1168
|
+
# success callback
|
1169
|
+
self.unreachable_func,
|
1170
|
+
# failure callback
|
1171
|
+
lambda move_line, message=None: self.assertEqual(
|
1172
|
+
message,
|
1173
|
+
self.msg_store.package_change_error_same_package(initial_package),
|
1174
|
+
),
|
1175
|
+
)
|