[FIX] stock: prevent opening SM when picking is dirty
Steps to reproduce ================== - Create two new storable products tracked by USN - Create a new RFQ with one of the created product - Confirm the order - Open the receipt - Add a new line with the other product - Click on the open move button in the new line - Add a new SN - Save & close => Cannot read properties of undefined (reading 'resId') Cause of the issue ================== When calling openRecord, if the record is dirty, it is saved before proceeding. After saving, we call super.openRecord with the old record. Since that record is no longer linked to the root record (the stock.picking), when we try to save it, it won't match an existing id. Solution ======== If the record is new, we don't save as there would be no way of knowing which of the returned line would come from this one. If we are opening an existing record, we find the new datapoint by matching it's ID. opw-3777615 closes odoo/odoo#162425 Signed-off-by: William Henrotin (whe) <whe@odoo.com>
This commit is contained in:
@@ -46,10 +46,14 @@ export class StockMoveX2ManyField extends X2ManyField {
|
||||
|
||||
|
||||
async openRecord(record) {
|
||||
if (this.canOpenRecord) {
|
||||
if (this.canOpenRecord && !record.isNew) {
|
||||
const dirty = await record.isDirty();
|
||||
if (dirty && 'quantity' in record._changes) {
|
||||
await record.model.root.save({ reload: true });
|
||||
record = record.model.root.data[this.props.name].records.find(e => e.resId === record.resId);
|
||||
if (!record) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.openRecord(record);
|
||||
|
||||
@@ -104,3 +104,55 @@ registry.category("web_tour.tours").add('test_generate_serial_1', { test: true,
|
||||
isCheck: true,
|
||||
},
|
||||
]});
|
||||
|
||||
registry.category("web_tour.tours").add('test_add_new_line', {
|
||||
test: true,
|
||||
steps: () => [
|
||||
{
|
||||
extra_trigger: '.o_form_editable',
|
||||
trigger: '.o_field_x2many_list_row_add > a'
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name=product_id] input",
|
||||
run: 'text two',
|
||||
},
|
||||
{ trigger: ".ui-menu-item > a:contains('Product two')" },
|
||||
{ trigger: ".fa-list:eq(1)" },
|
||||
{ trigger: "h4:contains('Stock move')" },
|
||||
{ trigger: '.o_field_x2many_list_row_add > a' },
|
||||
{
|
||||
trigger: ".o_field_widget[name=lot_name] input",
|
||||
run: 'text two',
|
||||
},
|
||||
{ trigger: ".o_form_view.modal-content .o_form_button_save" },
|
||||
{ trigger: ".o_form_view:not(.modal-content) .o_form_button_save" },
|
||||
{
|
||||
trigger: ".o_form_renderer.o_form_saved",
|
||||
isCheck: true,
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add('test_edit_existing_line', {
|
||||
test: true,
|
||||
steps: () => [
|
||||
{ trigger: ".o_data_cell[name=quantity]" },
|
||||
{
|
||||
trigger: ".o_field_widget[name=quantity] input",
|
||||
run: 'text 2',
|
||||
},
|
||||
{ trigger: ".fa-list" },
|
||||
{ trigger: "h4:contains('Stock move')" },
|
||||
{ trigger: ".o_data_cell[name=quantity]:eq(1)" },
|
||||
{
|
||||
trigger: ".o_field_widget[name=lot_name] input",
|
||||
run: 'text two',
|
||||
},
|
||||
{ trigger: ".o_form_view.modal-content .o_form_button_save" },
|
||||
{ trigger: ".o_form_view:not(.modal-content) .o_form_button_save" },
|
||||
{
|
||||
trigger: ".o_form_renderer.o_form_saved",
|
||||
isCheck: true,
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import Command
|
||||
from odoo.tests import HttpCase, tagged
|
||||
|
||||
|
||||
@@ -57,3 +58,63 @@ class TestStockPickingTour(HttpCase):
|
||||
('product_id', '=', product_serial.id),
|
||||
])
|
||||
self.assertEqual(len(serial), 5)
|
||||
|
||||
def test_add_new_line(self):
|
||||
product_one, _ = self.env["product.product"].create([
|
||||
{
|
||||
'name': 'Product one',
|
||||
'type': 'product',
|
||||
'tracking': 'serial',
|
||||
},
|
||||
{
|
||||
'name': 'Product two',
|
||||
'type': 'product',
|
||||
'tracking': 'serial',
|
||||
}
|
||||
])
|
||||
|
||||
self.receipt.write({
|
||||
"move_ids": [Command.create({
|
||||
"name": "test",
|
||||
"product_uom_qty": 1,
|
||||
"product_id": product_one.id,
|
||||
"location_id": self.receipt.location_id.id,
|
||||
"location_dest_id": self.receipt.location_dest_id.id,
|
||||
})]
|
||||
})
|
||||
|
||||
self.receipt.action_confirm()
|
||||
self.receipt.move_ids.move_line_ids.lot_name = "one"
|
||||
|
||||
url = self._get_picking_url(self.receipt.id)
|
||||
self.start_tour(url, 'test_add_new_line', login='admin', timeout=60)
|
||||
|
||||
names = self.receipt.move_ids.move_line_ids.mapped('lot_name')
|
||||
self.assertEqual(names, ["two", "one"])
|
||||
|
||||
def test_edit_existing_line(self):
|
||||
self.uom_unit = self.env.ref('uom.product_uom_unit')
|
||||
product_one = self.env['product.product'].create({
|
||||
'name': 'Product one',
|
||||
'type': 'product',
|
||||
'tracking': 'serial',
|
||||
})
|
||||
|
||||
self.receipt.write({
|
||||
"move_ids": [Command.create({
|
||||
"name": "test",
|
||||
"product_id": product_one.id,
|
||||
"location_id": self.receipt.location_id.id,
|
||||
"location_dest_id": self.receipt.location_dest_id.id,
|
||||
"product_uom_qty": 1,
|
||||
})]
|
||||
})
|
||||
|
||||
self.receipt.action_confirm()
|
||||
self.receipt.move_ids.move_line_ids.lot_name = "one"
|
||||
|
||||
url = self._get_picking_url(self.receipt.id)
|
||||
self.start_tour(url, 'test_edit_existing_line', login='admin', timeout=60)
|
||||
|
||||
names = self.receipt.move_ids.move_line_ids.mapped('lot_name')
|
||||
self.assertEqual(names, ["one", "two"])
|
||||
|
||||
Reference in New Issue
Block a user