[IMP] stock,mrp: detailed operation without RPCs

This commit replaces the opening of the stock moves detailed operation
wizard by the one2Many record preview. This means creating a move in a
picking is still done via a new line but the edition is done via the
`fa-list` button that open the record in the web client. The goal is to
reduce the RPCs call as much as possible. The stock move lines data are
stored in the stock move record until the picking save.

Additionally, this commit change a bit the immediate transfers flows.
The stock move show only initial demand (`product_uom_qty`) but the
column wording is still "Done". In the detailed operation view, the
stock move line `qty_done` is displayed as "Reserved".
At picking validation, the user is expected to enter the same quantity
in `product_uom_qty` and `quantity_done`. If `product_uom_qty` is equals
to 0, the done quantity is used as actual transfer quantity. If
`product_uom_qty` is different than 0 but small than the done quantity,
an error is raised.

Task: 3256447
Part-of: odoo/odoo#124409
This commit is contained in:
William Henrotin
2023-09-20 10:02:05 +00:00
parent 3d9cef5ead
commit 4da8c6ebca
27 changed files with 376 additions and 210 deletions
+1 -1
View File
@@ -361,7 +361,7 @@ class StockMove(models.Model):
if not bom: if not bom:
moves_ids_to_return.add(move.id) moves_ids_to_return.add(move.id)
continue continue
if move.picking_id.immediate_transfer or float_is_zero(move.product_uom_qty, precision_rounding=move.product_uom.rounding): if float_is_zero(move.product_uom_qty, precision_rounding=move.product_uom.rounding):
factor = move.product_uom._compute_quantity(move.quantity_done, bom.product_uom_id) / bom.product_qty factor = move.product_uom._compute_quantity(move.quantity_done, bom.product_uom_id) / bom.product_qty
else: else:
factor = move.product_uom._compute_quantity(move.product_uom_qty, bom.product_uom_id) / bom.product_qty factor = move.product_uom._compute_quantity(move.product_uom_qty, bom.product_uom_id) / bom.product_qty
+1 -1
View File
@@ -308,7 +308,7 @@ class TestKitPicking(common.TestMrpCommon):
move_receipt_1 = self.env['stock.move'].create({ move_receipt_1 = self.env['stock.move'].create({
'name': self.kit_parent.name, 'name': self.kit_parent.name,
'product_id': self.kit_parent.id, 'product_id': self.kit_parent.id,
'quantity_done': 3, 'product_uom_qty': 3,
'product_uom': self.kit_parent.uom_id.id, 'product_uom': self.kit_parent.uom_id.id,
'picking_id': picking.id, 'picking_id': picking.id,
'picking_type_id': self.env.ref('stock.picking_type_in').id, 'picking_type_id': self.env.ref('stock.picking_type_in').id,
@@ -393,8 +393,6 @@
context="{'default_company_id': company_id, 'default_product_id': product_id}" context="{'default_company_id': company_id, 'default_product_id': product_id}"
domain="[('product_id','=',product_id)]"/> domain="[('product_id','=',product_id)]"/>
<field name="group_id" column_invisible="True"/> <field name="group_id" column_invisible="True"/>
<button name="action_show_details" type="object" title="Show Details" icon="fa-list" context="{'default_product_uom_qty': 0}" invisible="not show_details_visible or has_tracking == 'none'"/>
<button class="o_optional_button btn btn-light" name="action_show_details" type="object" title="Show Details" icon="fa-list" context="{'default_product_uom_qty': 0}" invisible="has_tracking != 'none' or not show_details_visible"/>
</tree> </tree>
</field> </field>
</page> </page>
@@ -437,8 +435,6 @@
options="{'create': [('parent.use_create_components_lots', '!=', False)]}" options="{'create': [('parent.use_create_components_lots', '!=', False)]}"
context="{'default_company_id': company_id, 'default_product_id': product_id}" context="{'default_company_id': company_id, 'default_product_id': product_id}"
domain="[('product_id','=',product_id)]"/> domain="[('product_id','=',product_id)]"/>
<button name="action_show_details" type="object" title="Show Details" icon="fa-list" invisible="has_tracking == 'none' or not show_details_visible"/>
<button class="o_optional_button btn btn-light" name="action_show_details" type="object" title="Show Details" icon="fa-list" invisible="has_tracking != 'none' or not show_details_visible"/>
</tree> </tree>
</field> </field>
</page> </page>
@@ -38,8 +38,10 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
picking_form.partner_id = self.subcontractor_partner1 picking_form.partner_id = self.subcontractor_partner1
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = self.finished move.product_id = self.finished
move.quantity_done = 1 move.product_uom_qty = 1
picking_receipt = picking_form.save() picking_receipt = picking_form.save()
picking_receipt.action_reset_draft()
picking_receipt.action_confirm()
# Nothing should be tracked # Nothing should be tracked
self.assertTrue(all(m.product_uom_qty == m.reserved_availability for m in picking_receipt.move_ids)) self.assertTrue(all(m.product_uom_qty == m.reserved_availability for m in picking_receipt.move_ids))
@@ -379,8 +381,10 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
picking_form.partner_id = subcontractor_partner2 picking_form.partner_id = subcontractor_partner2
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = self.finished move.product_id = self.finished
move.quantity_done = 1 move.product_uom_qty = 1
picking_receipt = picking_form.save() picking_receipt = picking_form.save()
picking_receipt.action_reset_draft()
picking_receipt.action_confirm()
picking_receipt.move_ids.quantity_done = 3.0 picking_receipt.move_ids.quantity_done = 3.0
picking_receipt._action_done() picking_receipt._action_done()
@@ -405,7 +409,7 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
picking_form.partner_id = self.subcontractor_partner1 picking_form.partner_id = self.subcontractor_partner1
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = self.finished move.product_id = self.finished
move.quantity_done = 5 move.product_uom_qty = 5
picking_receipt = picking_form.save() picking_receipt = picking_form.save()
picking_receipt.action_reset_draft() picking_receipt.action_reset_draft()
picking_receipt.action_confirm() picking_receipt.action_confirm()
@@ -478,8 +482,11 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
picking_form.partner_id = subcontractor_contact picking_form.partner_id = subcontractor_contact
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = self.finished move.product_id = self.finished
move.quantity_done = 1 move.product_uom_qty = 1
picking_receipt = picking_form.save() picking_receipt = picking_form.save()
picking_receipt.action_reset_draft()
picking_receipt.action_confirm()
# Check that a manufacturing order is created # Check that a manufacturing order is created
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)]) mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
self.assertEqual(len(mo), 1) self.assertEqual(len(mo), 1)
@@ -493,8 +500,10 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
picking_form.partner_id = self.subcontractor_partner1 picking_form.partner_id = self.subcontractor_partner1
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = self.finished move.product_id = self.finished
move.quantity_done = 1 move.product_uom_qty = 1
picking_receipt = picking_form.save() picking_receipt = picking_form.save()
picking_receipt.action_reset_draft()
picking_receipt.action_confirm()
self.assertEqual(picking_receipt.display_action_record_components, 'facultative') self.assertEqual(picking_receipt.display_action_record_components, 'facultative')
action = picking_receipt.action_record_components() action = picking_receipt.action_record_components()
mo = self.env['mrp.production'].browse(action['res_id']) mo = self.env['mrp.production'].browse(action['res_id'])
@@ -525,8 +534,10 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
picking_form.partner_id = self.subcontractor_partner1 picking_form.partner_id = self.subcontractor_partner1
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = self.finished move.product_id = self.finished
move.quantity_done = 1 move.product_uom_qty = 1
picking_receipt = picking_form.save() picking_receipt = picking_form.save()
picking_receipt.action_reset_draft()
picking_receipt.action_confirm()
self.assertEqual(picking_receipt.display_action_record_components, 'facultative') self.assertEqual(picking_receipt.display_action_record_components, 'facultative')
action = picking_receipt.action_record_components() action = picking_receipt.action_record_components()
@@ -632,7 +643,7 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
picking_form.partner_id = self.subcontractor_partner1 picking_form.partner_id = self.subcontractor_partner1
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = finished move.product_id = finished
move.quantity_done = 5 move.product_uom_qty = 5
picking = picking_form.save() picking = picking_form.save()
picking.action_reset_draft() picking.action_reset_draft()
picking.action_confirm() picking.action_confirm()
@@ -1022,7 +1033,7 @@ class TestSubcontractingTracking(TransactionCase):
picking_form.partner_id = self.subcontractor_partner1 picking_form.partner_id = self.subcontractor_partner1
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = finished_product move.product_id = finished_product
move.quantity_done = todo_nb move.product_uom_qty = todo_nb
picking_receipt = picking_form.save() picking_receipt = picking_form.save()
picking_receipt.action_reset_draft() picking_receipt.action_reset_draft()
picking_receipt.action_confirm() picking_receipt.action_confirm()
@@ -1197,8 +1208,10 @@ class TestSubcontractingPortal(TransactionCase):
picking_form.partner_id = self.subcontractor_partner1 picking_form.partner_id = self.subcontractor_partner1
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = self.finished_product move.product_id = self.finished_product
move.quantity_done = 2 move.product_uom_qty = 2
picking_receipt = picking_form.save() picking_receipt = picking_form.save()
picking_receipt.action_reset_draft()
picking_receipt.action_confirm()
# Using the subcontractor (portal user) # Using the subcontractor (portal user)
lot1 = self.env['stock.lot'].with_user(self.portal_user).create({ lot1 = self.env['stock.lot'].with_user(self.portal_user).create({
@@ -1309,7 +1322,7 @@ class TestSubcontractingSerialMassReceipt(TransactionCase):
picking_form.partner_id = self.subcontractor picking_form.partner_id = self.subcontractor
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = self.finished move.product_id = self.finished
move.quantity_done = sum(quantities) move.product_uom_qty = sum(quantities)
picking_receipt = picking_form.save() picking_receipt = picking_form.save()
picking_receipt.action_reset_draft() picking_receipt.action_reset_draft()
picking_receipt.action_confirm() picking_receipt.action_confirm()
@@ -10,7 +10,7 @@
<button name="action_record_components" class="oe_highlight" invisible="display_action_record_components != 'mandatory'" string="Record components" type="object" data-hotkey="shift+x"/> <button name="action_record_components" class="oe_highlight" invisible="display_action_record_components != 'mandatory'" string="Record components" type="object" data-hotkey="shift+x"/>
<button name="action_record_components" invisible="display_action_record_components != 'facultative'" string="Record components" type="object" data-hotkey="shift+x"/> <button name="action_record_components" invisible="display_action_record_components != 'facultative'" string="Record components" type="object" data-hotkey="shift+x"/>
</xpath> </xpath>
<xpath expr="//field[@name='move_ids_without_package']//tree//button[@name='action_show_details']" position="after"> <xpath expr="//field[@name='move_ids_without_package']//tree" position="inside">
<field name="show_subcontracting_details_visible" column_invisible="True"/> <field name="show_subcontracting_details_visible" column_invisible="True"/>
<button name="action_show_subcontract_details" string="Register components for subcontracted product" type="object" icon="fa-sitemap" <button name="action_show_subcontract_details" string="Register components for subcontracted product" type="object" icon="fa-sitemap"
invisible="not show_subcontracting_details_visible"/> invisible="not show_subcontracting_details_visible"/>
@@ -305,7 +305,7 @@ class TestAccountSubcontractingFlows(TestMrpSubcontractingCommon):
receipt_form.partner_id = self.subcontractor_partner1 receipt_form.partner_id = self.subcontractor_partner1
with receipt_form.move_ids_without_package.new() as move: with receipt_form.move_ids_without_package.new() as move:
move.product_id = self.finished move.product_id = self.finished
move.quantity_done = 10 move.product_uom_qty = 10
receipt = receipt_form.save() receipt = receipt_form.save()
receipt.action_reset_draft() receipt.action_reset_draft()
# add an extra cost # add an extra cost
@@ -11,6 +11,17 @@
</xpath> </xpath>
</field> </field>
</record> </record>
<record id="view_stock_move_operations_expiry_immediate" model="ir.ui.view">
<field name="name">stock.move.operations.inherit.form</field>
<field name="model">stock.move</field>
<field name="inherit_id" ref="stock.view_stock_move_operations_immediate"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='product_id']" position="after" >
<field name="picking_code" invisible="1"/>
<field name="use_expiration_date" invisible="1"/>
</xpath>
</field>
</record>
<record id="view_stock_move_line_operation_tree_expiry" model="ir.ui.view"> <record id="view_stock_move_line_operation_tree_expiry" model="ir.ui.view">
<field name="name">stock.move.line.inherit.tree</field> <field name="name">stock.move.line.inherit.tree</field>
@@ -66,7 +66,7 @@ class TestReorderingRule(TransactionCase):
picking_form.picking_type_id = self.env.ref('stock.picking_type_out') picking_form.picking_type_id = self.env.ref('stock.picking_type_out')
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = self.product_01 move.product_id = self.product_01
move.quantity_done = 10.0 move.product_uom_qty = 10.0
customer_picking = picking_form.save() customer_picking = picking_form.save()
# Run scheduler # Run scheduler
self.env['procurement.group'].run_scheduler() self.env['procurement.group'].run_scheduler()
@@ -141,10 +141,10 @@ class TestReorderingRule(TransactionCase):
picking_form.picking_type_id = self.env.ref('stock.picking_type_out') picking_form.picking_type_id = self.env.ref('stock.picking_type_out')
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = self.product_01 move.product_id = self.product_01
move.quantity_done = 10.0 move.product_uom_qty = 10.0
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = self.product_01 move.product_id = self.product_01
move.quantity_done = 10.0 move.product_uom_qty = 10.0
customer_picking = picking_form.save() customer_picking = picking_form.save()
customer_picking.move_ids[0].location_id = subloc_1.id customer_picking.move_ids[0].location_id = subloc_1.id
customer_picking.move_ids[1].location_id = subloc_2.id customer_picking.move_ids[1].location_id = subloc_2.id
+1 -1
View File
@@ -112,7 +112,7 @@
'stock/static/src/scss/stock_traceability_report.scss', 'stock/static/src/scss/stock_traceability_report.scss',
], ],
'web.assets_tests': [ 'web.assets_tests': [
'stock/static/tests/tours/stock_report_tests.js', 'stock/static/tests/tours/*.js',
], ],
'web.qunit_suite_tests': [ 'web.qunit_suite_tests': [
'stock/static/tests/inventory_report_list_tests.js', 'stock/static/tests/inventory_report_list_tests.js',
+36 -33
View File
@@ -132,7 +132,7 @@ class StockMove(models.Model):
'Propagate cancel and split', default=True, 'Propagate cancel and split', default=True,
help='If checked, when this move is cancelled, cancel the linked move too') help='If checked, when this move is cancelled, cancel the linked move too')
delay_alert_date = fields.Datetime('Delay Alert Date', help='Process at this date to be on time', compute="_compute_delay_alert_date", store=True) delay_alert_date = fields.Datetime('Delay Alert Date', help='Process at this date to be on time', compute="_compute_delay_alert_date", store=True)
picking_type_id = fields.Many2one('stock.picking.type', 'Operation Type', compute='_compute_picking_type_id', store=True, check_company=True) picking_type_id = fields.Many2one('stock.picking.type', 'Operation Type', compute='_compute_picking_type_id', store=True, readonly=False, check_company=True)
is_inventory = fields.Boolean('Inventory') is_inventory = fields.Boolean('Inventory')
move_line_ids = fields.One2many('stock.move.line', 'move_id') move_line_ids = fields.One2many('stock.move.line', 'move_id')
move_line_nosuggest_ids = fields.One2many('stock.move.line', 'move_id', domain=['|', ('reserved_qty', '=', 0.0), ('qty_done', '!=', 0.0)]) move_line_nosuggest_ids = fields.One2many('stock.move.line', 'move_id', domain=['|', ('reserved_qty', '=', 0.0), ('qty_done', '!=', 0.0)])
@@ -173,7 +173,6 @@ class StockMove(models.Model):
picking_type_entire_packs = fields.Boolean(related='picking_type_id.show_entire_packs', readonly=True) picking_type_entire_packs = fields.Boolean(related='picking_type_id.show_entire_packs', readonly=True)
display_assign_serial = fields.Boolean(compute='_compute_display_assign_serial') display_assign_serial = fields.Boolean(compute='_compute_display_assign_serial')
display_import_lot = fields.Boolean(compute='_compute_display_assign_serial') display_import_lot = fields.Boolean(compute='_compute_display_assign_serial')
display_clear_serial = fields.Boolean(compute='_compute_display_clear_serial')
next_serial = fields.Char('First SN') next_serial = fields.Char('First SN')
next_serial_count = fields.Integer('Number of SN') next_serial_count = fields.Integer('Number of SN')
orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Original Reordering Rule', index=True) orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Original Reordering Rule', index=True)
@@ -185,6 +184,9 @@ class StockMove(models.Model):
product_packaging_id = fields.Many2one('product.packaging', 'Packaging', domain="[('product_id', '=', product_id)]", check_company=True) product_packaging_id = fields.Many2one('product.packaging', 'Packaging', domain="[('product_id', '=', product_id)]", check_company=True)
from_immediate_transfer = fields.Boolean(related="picking_id.immediate_transfer") from_immediate_transfer = fields.Boolean(related="picking_id.immediate_transfer")
show_reserved = fields.Boolean(compute='_compute_show_reserved') show_reserved = fields.Boolean(compute='_compute_show_reserved')
show_quant = fields.Boolean("Show Quant", compute="_compute_show_info")
show_lots_m2o = fields.Boolean("Show lot_id", compute="_compute_show_info")
show_lots_text = fields.Boolean("Show lot_name", compute="_compute_show_info")
@api.depends('product_id') @api.depends('product_id')
def _compute_product_uom(self): def _compute_product_uom(self):
@@ -197,16 +199,11 @@ class StockMove(models.Model):
move.display_import_lot = ( move.display_import_lot = (
move.has_tracking != 'none' and move.has_tracking != 'none' and
move.picking_type_id.use_create_lots and move.picking_type_id.use_create_lots and
not move.origin_returned_move_id.id not move.origin_returned_move_id.id and
move.state not in ('done', 'cancel')
) )
move.display_assign_serial = move.has_tracking == 'serial' and move.display_import_lot move.display_assign_serial = move.has_tracking == 'serial' and move.display_import_lot
@api.depends('display_assign_serial', 'move_line_ids', 'move_line_nosuggest_ids')
def _compute_display_clear_serial(self):
self.display_clear_serial = False
for move in self:
move.display_clear_serial = move.display_assign_serial and move._get_move_lines()
@api.depends('picking_id.priority') @api.depends('picking_id.priority')
def _compute_priority(self): def _compute_priority(self):
for move in self: for move in self:
@@ -255,15 +252,16 @@ class StockMove(models.Model):
for move in self: for move in self:
move.show_reserved_availability = not move.location_id.usage == 'supplier' move.show_reserved_availability = not move.location_id.usage == 'supplier'
@api.depends('state', 'picking_id') @api.depends('state', 'picking_id.is_locked', 'picking_id.immediate_transfer')
def _compute_is_initial_demand_editable(self): def _compute_is_initial_demand_editable(self):
self.is_initial_demand_editable = False
for move in self: for move in self:
if not move.picking_id.immediate_transfer and move.state == 'draft': if move.state == 'done' and move.picking_id.is_locked:
move.is_initial_demand_editable = True move.is_initial_demand_editable = True
elif not move.picking_id.is_locked and move.state != 'done' and move.picking_id: elif move.state not in ('done', 'cancel') and move.picking_id.immediate_transfer:
move.is_initial_demand_editable = True
elif move.state == 'draft':
move.is_initial_demand_editable = True move.is_initial_demand_editable = True
else:
move.is_initial_demand_editable = False
@api.depends('state', 'picking_id', 'product_id') @api.depends('state', 'picking_id', 'product_id')
def _compute_is_quantity_done_editable(self): def _compute_is_quantity_done_editable(self):
@@ -536,7 +534,7 @@ Please change the quantity done or the rounding precision of your unit of measur
else: else:
move_update.date_deadline = new_deadline move_update.date_deadline = new_deadline
@api.depends('move_line_ids', 'move_line_ids.lot_id', 'move_line_ids.qty_done') @api.depends('move_line_ids.lot_id', 'move_line_ids.qty_done')
def _compute_lot_ids(self): def _compute_lot_ids(self):
domain_nosuggest = [('move_id', 'in', self.ids), ('lot_id', '!=', False), '|', ('qty_done', '!=', 0.0), ('reserved_qty', '=', 0.0)] domain_nosuggest = [('move_id', 'in', self.ids), ('lot_id', '!=', False), '|', ('qty_done', '!=', 0.0), ('reserved_qty', '=', 0.0)]
domain_suggest = [('move_id', 'in', self.ids), ('lot_id', '!=', False), ('qty_done', '!=', 0.0)] domain_suggest = [('move_id', 'in', self.ids), ('lot_id', '!=', False), ('qty_done', '!=', 0.0)]
@@ -588,7 +586,7 @@ Please change the quantity done or the rounding precision of your unit of measur
move_line.qty_done = 1 move_line.qty_done = 1
move.write({'move_line_ids': move_lines_commands}) move.write({'move_line_ids': move_lines_commands})
@api.depends('picking_type_id', 'date', 'priority') @api.depends('picking_type_id', 'date', 'priority', 'state')
def _compute_reservation_date(self): def _compute_reservation_date(self):
for move in self: for move in self:
if move.picking_type_id.reservation_method == 'by_date' and move.state in ['draft', 'confirmed', 'waiting', 'partially_available']: if move.picking_type_id.reservation_method == 'by_date' and move.state in ['draft', 'confirmed', 'waiting', 'partially_available']:
@@ -597,6 +595,20 @@ Please change the quantity done or the rounding precision of your unit of measur
days = move.picking_type_id.reservation_days_before_priority days = move.picking_type_id.reservation_days_before_priority
move.reservation_date = fields.Date.to_date(move.date) - timedelta(days=days) move.reservation_date = fields.Date.to_date(move.date) - timedelta(days=days)
@api.depends('has_tracking', 'picking_type_id.use_create_lots', 'picking_type_id.use_existing_lots', 'state', 'origin_returned_move_id', 'product_id.detailed_type', 'picking_code')
def _compute_show_info(self):
for move in self:
move.show_quant = move.picking_code != 'incoming'\
and move.product_id.detailed_type == 'product'
move.show_lots_m2o = not move.show_quant\
and move.has_tracking != 'none'\
and (move.picking_type_id.use_existing_lots or move.state == 'done' or move.origin_returned_move_id.id)
move.show_lots_text = move.has_tracking != 'none'\
and move.picking_type_id.use_create_lots\
and not move.picking_type_id.use_existing_lots \
and move.state != 'done' \
and not move.origin_returned_move_id.id
@api.constrains('product_uom') @api.constrains('product_uom')
def _check_uom(self): def _check_uom(self):
moves_error = self.filtered(lambda move: move.product_id.uom_id.category_id != move.product_uom.category_id) moves_error = self.filtered(lambda move: move.product_id.uom_id.category_id != move.product_uom.category_id)
@@ -643,7 +655,7 @@ Please change the quantity done or the rounding precision of your unit of measur
@api.model_create_multi @api.model_create_multi
def create(self, vals_list): def create(self, vals_list):
for vals in vals_list: for vals in vals_list:
if vals.get('quantity_done') and 'lot_ids' in vals: if (vals.get('quantity_done') or vals.get('move_line_ids')) and 'lot_ids' in vals:
vals.pop('lot_ids') vals.pop('lot_ids')
picking_id = self.env['stock.picking'].browse(vals.get('picking_id')) picking_id = self.env['stock.picking'].browse(vals.get('picking_id'))
if picking_id and picking_id.immediate_transfer and not vals.get('qty_done'): if picking_id and picking_id.immediate_transfer and not vals.get('qty_done'):
@@ -787,14 +799,11 @@ Please change the quantity done or the rounding precision of your unit of measur
# reserved move lines. We do this by displaying `move_line_nosuggest_ids`. We use # reserved move lines. We do this by displaying `move_line_nosuggest_ids`. We use
# different views to display one field or another so that the webclient doesn't have to # different views to display one field or another so that the webclient doesn't have to
# fetch both. # fetch both.
if self.show_reserved: if self.from_immediate_transfer:
view = self.env.ref('stock.view_stock_move_operations') view = self.env.ref('stock.view_stock_move_operations_immediate')
else: else:
view = self.env.ref('stock.view_stock_move_nosuggest_operations') view = self.env.ref('stock.view_stock_move_operations')
if self.product_id.tracking == "serial" and self.state == "assigned":
self.next_serial = self.env['stock.lot']._get_next_serial(self.company_id, self.product_id)
quant_mode = self.picking_type_id.code != 'incoming' and self.product_id.detailed_type == 'product'
return { return {
'name': _('Detailed Operations'), 'name': _('Detailed Operations'),
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
@@ -806,13 +815,6 @@ Please change the quantity done or the rounding precision of your unit of measur
'res_id': self.id, 'res_id': self.id,
'context': dict( 'context': dict(
self.env.context, self.env.context,
show_owner=not quant_mode,
show_quant=quant_mode,
show_lots_m2o=not quant_mode and self.has_tracking != 'none' and (self.picking_type_id.use_existing_lots or self.state == 'done' or self.origin_returned_move_id.id), # able to create lots, whatever the value of ` use_create_lots`.
show_lots_text=self.has_tracking != 'none' and self.picking_type_id.use_create_lots and not self.picking_type_id.use_existing_lots and self.state != 'done' and not self.origin_returned_move_id.id,
show_destination_location=not quant_mode,
show_package=not quant_mode,
show_reserved_quantity=self.state != 'done' and quant_mode
), ),
} }
@@ -1820,7 +1822,7 @@ Please change the quantity done or the rounding precision of your unit of measur
new_push_moves._action_confirm() new_push_moves._action_confirm()
def _action_done(self, cancel_backorder=False): def _action_done(self, cancel_backorder=False):
moves = self.filtered(lambda move: move.state == 'draft')._action_confirm() # MRP allows scrapping draft moves moves = self.filtered(lambda move: move.state == 'draft' or move.state == 'assigned' and move.from_immediate_transfer)._action_confirm() # MRP allows scrapping draft moves
moves = (self | moves).exists().filtered(lambda x: x.state not in ('done', 'cancel')) moves = (self | moves).exists().filtered(lambda x: x.state not in ('done', 'cancel'))
moves_ids_todo = OrderedSet() moves_ids_todo = OrderedSet()
@@ -1931,7 +1933,8 @@ Please change the quantity done or the rounding precision of your unit of measur
# we restrict the split of a draft move because if not confirmed yet, it may be replaced by several other moves in # we restrict the split of a draft move because if not confirmed yet, it may be replaced by several other moves in
# case of phantom bom (with mrp module). And we don't want to deal with this complexity by copying the product that will explode. # case of phantom bom (with mrp module). And we don't want to deal with this complexity by copying the product that will explode.
raise UserError(_('You cannot split a draft move. It needs to be confirmed first.')) raise UserError(_('You cannot split a draft move. It needs to be confirmed first.'))
if float_is_zero(qty, precision_rounding=self.product_id.uom_id.rounding) or self.product_qty <= qty: # exclude immediate transfer in case we want to add a move line in a wave picking
if float_is_zero(qty, precision_rounding=self.product_id.uom_id.rounding) or self.product_qty <= qty and not self.from_immediate_transfer:
return [] return []
decimal_precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') decimal_precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
@@ -1957,7 +1960,7 @@ Please change the quantity done or the rounding precision of your unit of measur
# Update the original `product_qty` of the move. Use the general product's decimal # Update the original `product_qty` of the move. Use the general product's decimal
# precision and not the move's UOM to handle case where the `quantity_done` is not # precision and not the move's UOM to handle case where the `quantity_done` is not
# compatible with the move's UOM. # compatible with the move's UOM.
new_product_qty = self.product_id.uom_id._compute_quantity(self.product_qty - qty, self.product_uom, round=False) new_product_qty = self.product_id.uom_id._compute_quantity(max(0, self.product_qty - qty), self.product_uom, round=False)
new_product_qty = float_round(new_product_qty, precision_digits=self.env['decimal.precision'].precision_get('Product Unit of Measure')) new_product_qty = float_round(new_product_qty, precision_digits=self.env['decimal.precision'].precision_get('Product Unit of Measure'))
self.with_context(do_not_unreserve=True).write({'product_uom_qty': new_product_qty}) self.with_context(do_not_unreserve=True).write({'product_uom_qty': new_product_qty})
return new_move_vals return new_move_vals
+7 -9
View File
@@ -140,8 +140,7 @@ class StockMoveLine(models.Model):
if (record.move_id.product_qty - record.move_id.quantity_done): if (record.move_id.product_qty - record.move_id.quantity_done):
record.qty_done = min(record.quant_id.available_quantity, max(record.move_id.product_qty - record.move_id.quantity_done, 0)) record.qty_done = min(record.quant_id.available_quantity, max(record.move_id.product_qty - record.move_id.quantity_done, 0))
else: else:
record.qty_done = record.quant_id.quantity record.qty_done = record.quant_id.available_quantity
@api.constrains('lot_id', 'product_id') @api.constrains('lot_id', 'product_id')
def _check_lot_product(self): def _check_lot_product(self):
@@ -319,7 +318,10 @@ class StockMoveLine(models.Model):
moves = move_line.picking_id.move_ids.filtered(lambda x: x.product_id == move_line.product_id) moves = move_line.picking_id.move_ids.filtered(lambda x: x.product_id == move_line.product_id)
moves = sorted(moves, key=lambda m: m.quantity_done < m.product_qty, reverse=True) moves = sorted(moves, key=lambda m: m.quantity_done < m.product_qty, reverse=True)
if moves: if moves:
move_line.move_id = moves[0].id move_line.write({
'move_id': moves[0].id,
'picking_id': moves[0].picking_id.id,
})
else: else:
create_move(move_line) create_move(move_line)
else: else:
@@ -352,11 +354,8 @@ class StockMoveLine(models.Model):
moves_to_update = mls.filtered( moves_to_update = mls.filtered(
lambda ml: lambda ml:
ml.move_id and ml.move_id and
ml.qty_done and ( ml.qty_done and
ml.move_id.state == 'done' or ( ml.move_id.state == 'done'
ml.move_id.picking_id and
ml.move_id.picking_id.immediate_transfer
))
).move_id ).move_id
for move in moves_to_update: for move in moves_to_update:
move.with_context(avoid_putaway_rules=True).product_uom_qty = move.quantity_done move.with_context(avoid_putaway_rules=True).product_uom_qty = move.quantity_done
@@ -512,7 +511,6 @@ class StockMoveLine(models.Model):
# this is what move's `action_done` will do. So, we replicate the behavior here. # this is what move's `action_done` will do. So, we replicate the behavior here.
if updates or 'qty_done' in vals: if updates or 'qty_done' in vals:
moves = self.filtered(lambda ml: ml.move_id.state == 'done').mapped('move_id') moves = self.filtered(lambda ml: ml.move_id.state == 'done').mapped('move_id')
moves |= self.filtered(lambda ml: ml.move_id.state not in ('done', 'cancel') and ml.move_id.picking_id.immediate_transfer).mapped('move_id')
for move in moves: for move in moves:
move.product_uom_qty = move.quantity_done move.product_uom_qty = move.quantity_done
next_moves._do_unreserve() next_moves._do_unreserve()
+18 -4
View File
@@ -405,8 +405,8 @@ class Picking(models.Model):
compute="_compute_location_id", store=True, precompute=True, readonly=False, compute="_compute_location_id", store=True, precompute=True, readonly=False,
check_company=True, required=True) check_company=True, required=True)
move_ids = fields.One2many('stock.move', 'picking_id', string="Stock Moves", copy=True) move_ids = fields.One2many('stock.move', 'picking_id', string="Stock Moves", copy=True)
move_ids_without_package = fields.One2many( move_ids_without_package = fields.Many2many(
'stock.move', 'picking_id', string="Stock moves not in package", compute='_compute_move_without_package', 'stock.move', string="Stock moves not in package", compute='_compute_move_without_package',
inverse='_set_move_without_package', compute_sudo=True) inverse='_set_move_without_package', compute_sudo=True)
has_scrap_move = fields.Boolean( has_scrap_move = fields.Boolean(
'Has Scrap Moves', compute='_has_scrap_move') 'Has Scrap Moves', compute='_has_scrap_move')
@@ -864,7 +864,7 @@ class Picking(models.Model):
after_vals['partner_id'] = vals['partner_id'] after_vals['partner_id'] = vals['partner_id']
if after_vals: if after_vals:
self.move_ids.filtered(lambda move: not move.scrapped).write(after_vals) self.move_ids.filtered(lambda move: not move.scrapped).write(after_vals)
if vals.get('move_ids'): if vals.get('move_ids') or vals.get('move_ids_without_package'):
self._autoconfirm_picking() self._autoconfirm_picking()
return res return res
@@ -1093,7 +1093,7 @@ class Picking(models.Model):
no_quantities_done_ids.add(picking.id) no_quantities_done_ids.add(picking.id)
if all(float_is_zero(move_line.reserved_qty, precision_rounding=move_line.product_uom_id.rounding) for move_line in picking.move_line_ids): if all(float_is_zero(move_line.reserved_qty, precision_rounding=move_line.product_uom_id.rounding) for move_line in picking.move_line_ids):
no_reserved_quantities_ids.add(picking.id) no_reserved_quantities_ids.add(picking.id)
pickings_without_quantities = self.filtered(lambda p: p.id in no_quantities_done_ids and p.id in no_reserved_quantities_ids) pickings_without_quantities = self.filtered(lambda p: p.id in no_quantities_done_ids and p.id in no_reserved_quantities_ids and not p.immediate_transfer)
pickings_using_lots = self.filtered(lambda p: p.picking_type_id.use_create_lots or p.picking_type_id.use_existing_lots) pickings_using_lots = self.filtered(lambda p: p.picking_type_id.use_create_lots or p.picking_type_id.use_existing_lots)
if pickings_using_lots: if pickings_using_lots:
@@ -1190,6 +1190,18 @@ class Picking(models.Model):
pickings_to_backorder = self._check_backorder() pickings_to_backorder = self._check_backorder()
if pickings_to_backorder: if pickings_to_backorder:
return pickings_to_backorder._action_generate_backorder_wizard(show_transfers=self._should_show_transfers()) return pickings_to_backorder._action_generate_backorder_wizard(show_transfers=self._should_show_transfers())
message = ''
for move in self.move_ids:
if float_compare(move.quantity_done, move.product_uom_qty, precision_rounding=move.product_uom.rounding) > 0 \
and not float_is_zero(move.product_uom_qty, precision_rounding=move.product_uom.rounding) \
and move.from_immediate_transfer:
message += _('%(name)s has a quantity of %(initial_qty)s %(uom)s done, but %(done_qty)s %(uom)s reserved\n', name=move.product_id.display_name, initial_qty=move.product_uom_qty, done_qty=move.quantity_done, uom=move.product_uom.name)
if move.from_immediate_transfer and float_compare(move.product_qty, move.quantity_done, precision_rounding=move.product_uom.rounding) > 0:
move.quantity_done = move.product_qty
if message:
message = _('The product(s)\n') + message + _('Update quantities of those products.')
raise UserError(message)
return True return True
def _should_show_transfers(self): def _should_show_transfers(self):
@@ -1265,6 +1277,8 @@ class Picking(models.Model):
immediate_pickings = self.browse() immediate_pickings = self.browse()
precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure') precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure')
for picking in self: for picking in self:
if picking.immediate_transfer:
continue
if all(float_is_zero(move_line.qty_done, precision_digits=precision_digits) for move_line in picking.move_line_ids.filtered(lambda m: m.state not in ('done', 'cancel'))): if all(float_is_zero(move_line.qty_done, precision_digits=precision_digits) for move_line in picking.move_line_ids.filtered(lambda m: m.state not in ('done', 'cancel'))):
immediate_pickings |= picking immediate_pickings |= picking
return immediate_pickings return immediate_pickings
+3 -3
View File
@@ -545,11 +545,11 @@ class StockQuant(models.Model):
"""name that will be displayed in the detailed operation""" """name that will be displayed in the detailed operation"""
for record in self: for record in self:
name = [record.location_id.display_name] name = [record.location_id.display_name]
if self.env.user.has_group('stock.group_production_lot') and record.lot_id: if record.lot_id:
name.append(record.lot_id.name) name.append(record.lot_id.name)
if self.env.user.has_group('stock.group_tracking_lot') and record.package_id: if record.package_id:
name.append(record.package_id.name) name.append(record.package_id.name)
if self.env.user.has_group('stock.group_tracking_owner') and record.owner_id: if record.owner_id:
name.append(record.owner_id.name) name.append(record.owner_id.name)
record.display_name = ' - '.join(name) record.display_name = ' - '.join(name)
@@ -18,7 +18,7 @@ export class SMLX2ManyField extends X2ManyField {
this.selectCreate = (params) => { this.selectCreate = (params) => {
return selectCreate(params); return selectCreate(params);
}; };
this.openRecord = useOpenMany2XRecord({ this.openQuantRecord = useOpenMany2XRecord({
resModel: "stock.quant", resModel: "stock.quant",
activeActions: this.activeActions, activeActions: this.activeActions,
onRecordSaved: (record) => this.selectRecord([record.resId]), onRecordSaved: (record) => this.selectRecord([record.resId]),
@@ -29,6 +29,9 @@ export class SMLX2ManyField extends X2ManyField {
} }
async onAdd({ context, editable } = {}) { async onAdd({ context, editable } = {}) {
if (!this.props.record.data.show_quant) {
return super.onAdd(...arguments);
}
context = { context = {
...context, ...context,
single_product: true, single_product: true,
@@ -51,12 +54,16 @@ export class SMLX2ManyField extends X2ManyField {
const params = { const params = {
context: { default_quant_id: res_ids[0] }, context: { default_quant_id: res_ids[0] },
}; };
this.addInLine(params); this.list.addNewRecord(params).then((record) => {
// Make it dirty to force the save of the record. addNewRecord make
// the new record dirty === False by default to remove them at unfocus event
record.dirty = true;
});
} }
createOpenRecord() { createOpenRecord() {
const activeElement = document.activeElement; const activeElement = document.activeElement;
this.openRecord({ this.openQuantRecord({
context: { context: {
...this.props.context, ...this.props.context,
form_view_ref: "stock.view_stock_quant_form", form_view_ref: "stock.view_stock_quant_form",
@@ -3,31 +3,22 @@
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { ListRenderer } from "@web/views/list/list_renderer"; import { ListRenderer } from "@web/views/list/list_renderer";
import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field"; import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field";
import { ViewButton } from "@web/views/view_button/view_button";
class MoveViewButton extends ViewButton { export class MovesListRenderer extends ListRenderer {}
async onClick(ev) {
if (this.props.clickParams.name != "action_show_details") { MovesListRenderer.props = [ ...ListRenderer.props, 'stockMoveOpen?']
super.onClick(ev);
} else { export class StockMoveX2ManyField extends X2ManyField {
await this.props.record.saveAndOpenDetails(); setup() {
} super.setup();
this.canOpenRecord = true;
} }
get disabled() { get isMany2Many() {
if (this.props.clickParams.name == "action_show_details") { return false;
return false;
}
return super.disabled;
} }
} }
MoveViewButton.props = [...ViewButton.props];
export class MovesListRenderer extends ListRenderer {}
MovesListRenderer.components = { ...ListRenderer.components, ViewButton: MoveViewButton };
export class StockMoveX2ManyField extends X2ManyField {}
StockMoveX2ManyField.components = { ...X2ManyField.components, ListRenderer: MovesListRenderer }; StockMoveX2ManyField.components = { ...X2ManyField.components, ListRenderer: MovesListRenderer };
export const stockMoveX2ManyField = { export const stockMoveX2ManyField = {
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-inherit-mode="extension" t-inherit="web.ListRenderer.RecordRow" owl="1">
<xpath expr="//tr/t[1]" position="after">
<t t-if="record.resModel === 'stock.move' &amp;&amp; record.data?.show_details_visible">
<td class="o_data_cell cursor-pointer o_list_button">
<t t-if="record.resModel === 'stock.move' &amp;&amp; record.data?.show_details_visible">
<button name="Open Move" class="btn btn-link fa fa-list" t-on-click="() => props.openRecord(record)"/>
</t>
</td>
</t>
</xpath>
</t>
</templates>
@@ -0,0 +1,28 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
registry.category("web_tour.tours").add('test_detailed_op_no_save_1', { test: true, steps: () => [
{trigger: '.o_field_x2many_list_row_add > a'},
{
trigger: ".o_field_widget[name=product_id] input",
run: 'text Lot',
},
{trigger: ".ui-menu-item > a:contains('Product Lot')"},
{trigger: ".fa-list"},
{trigger: "h4:contains('Stock moves not in package')"},
{trigger: '.o_field_x2many_list_row_add > a'},
{
trigger: ".o_field_widget[name=lot_name] input",
run: 'text lot1',
},
{
trigger: ".o_field_widget[name=qty_done] input",
run: 'text 4',
},
{trigger: ".o_form_button_save"},
{trigger: ".btn-primary[name=button_validate]"},
{
trigger: ".o_control_panel_actions button:contains('Traceability')",
isCheck: true,
},
]});
+1
View File
@@ -24,3 +24,4 @@ from . import test_report_tours
from . import test_stock_return_picking from . import test_stock_return_picking
from . import test_stock_lot from . import test_stock_lot
from . import test_picking from . import test_picking
from . import test_picking_tours
+2 -4
View File
@@ -37,10 +37,8 @@ class StockMove(TransactionCase):
move.product_uom_qty = 1.0 move.product_uom_qty = 1.0
picking = picking.save() picking = picking.save()
action = picking.move_ids[0].action_show_details() self.assertEqual(picking.move_ids[0].show_quant, True)
self.assertEqual(action['context']['show_quant'], True) self.assertEqual(picking.move_ids[1].show_quant, False)
action = picking.move_ids[1].action_show_details()
self.assertEqual(action['context']['show_quant'], False)
def test_create_move_line_reserved(self): def test_create_move_line_reserved(self):
""" Create a delivery immediate transfer with a storable product. """ Create a delivery immediate transfer with a storable product.
+3 -3
View File
@@ -57,7 +57,7 @@ class TestPickShip(TestStockCommon):
def create_pick_pack_ship(self): def create_pick_pack_ship(self):
picking_ship = self.env['stock.picking'].create({ picking_ship = self.env['stock.picking'].create({
'location_id': self.pack_location, 'location_id': self.output_location,
'location_dest_id': self.customer_location, 'location_dest_id': self.customer_location,
'picking_type_id': self.picking_type_out, 'picking_type_id': self.picking_type_out,
'state': 'draft', 'state': 'draft',
@@ -75,8 +75,8 @@ class TestPickShip(TestStockCommon):
}) })
picking_pack = self.env['stock.picking'].create({ picking_pack = self.env['stock.picking'].create({
'location_id': self.stock_location, 'location_id': self.pack_location,
'location_dest_id': self.pack_location, 'location_dest_id': self.output_location,
'picking_type_id': self.picking_type_out, 'picking_type_id': self.picking_type_out,
'state': 'draft', 'state': 'draft',
'immediate_transfer': False, 'immediate_transfer': False,
+5 -4
View File
@@ -470,10 +470,10 @@ class TestPacking(TestPackingCommon):
# Add 2 lines # Add 2 lines
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.productA move_line.product_id = self.productA
move_line.quantity_done = 1 move_line.product_uom_qty = 1
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.productB move_line.product_id = self.productB
move_line.quantity_done = 1 move_line.product_uom_qty = 1
receipt = receipt_form.save() receipt = receipt_form.save()
receipt.action_reset_draft() receipt.action_reset_draft()
receipt.action_confirm() receipt.action_confirm()
@@ -622,10 +622,10 @@ class TestPacking(TestPackingCommon):
# Add 2 lines # Add 2 lines
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.productA move_line.product_id = self.productA
move_line.quantity_done = 1 move_line.product_uom_qty = 1
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.productB move_line.product_id = self.productB
move_line.quantity_done = 1 move_line.product_uom_qty = 1
receipt = receipt_form.save() receipt = receipt_form.save()
receipt.action_reset_draft() receipt.action_reset_draft()
receipt.action_confirm() receipt.action_confirm()
@@ -906,6 +906,7 @@ class TestPacking(TestPackingCommon):
self.assertEqual(len(picking.move_ids), 1, 'Should have only 1 stock move') self.assertEqual(len(picking.move_ids), 1, 'Should have only 1 stock move')
def test_picking_state_with_null_qty(self): def test_picking_state_with_null_qty(self):
""" Exclude empty stock move of the picking state computation """
delivery_form = Form(self.env['stock.picking']) delivery_form = Form(self.env['stock.picking'])
picking_type_id = self.warehouse.out_type_id picking_type_id = self.warehouse.out_type_id
delivery_form.picking_type_id = picking_type_id delivery_form.picking_type_id = picking_type_id
+38
View File
@@ -0,0 +1,38 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import HttpCase, tagged
@tagged('-at_install', 'post_install')
class TestStockPickingTour(HttpCase):
def setUp(self):
self.receipt = self.env['stock.picking'].create({
'picking_type_id': self.env.ref('stock.picking_type_in').id,
'location_id': self.env.ref('stock.stock_location_suppliers').id,
'location_dest_id': self.env.ref('stock.stock_location_stock').id,
})
return super().setUp()
def _get_picking_url(self, picking_id):
action = self.env["ir.actions.actions"]._for_xml_id("stock.action_picking_tree_incoming")
return '/web#action=%s&id=%s&model=stock.picking&view_type=form' % (action['id'], picking_id)
def test_detailed_op_no_save_1(self):
"""validate a receipt with some move without any save except the last one"""
product_lot = self.env['product.product'].create({
'name': 'Product Lot',
'type': 'product',
'tracking': 'lot',
})
url = self._get_picking_url(self.receipt.id)
self.start_tour(url, 'test_detailed_op_no_save_1', login='admin', timeout=60)
self.assertEqual(self.receipt.state, 'done')
self.assertEqual(self.receipt.move_ids.product_uom_qty, 0)
self.assertEqual(self.receipt.move_ids.quantity_done, 4)
lot = self.env['stock.lot'].search([
('name', '=', 'lot1'),
('product_id', '=', product_lot.id),
])
self.assertEqual(len(lot), 1)
self.assertEqual(lot.product_qty, 4)
+55 -37
View File
@@ -319,7 +319,7 @@ class TestReports(TestReportsCommon):
receipt = receipt_form.save() receipt = receipt_form.save()
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 2 move_line.product_uom_qty = 2
receipt = receipt_form.save() receipt = receipt_form.save()
receipt.action_reset_draft() receipt.action_reset_draft()
@@ -338,7 +338,7 @@ class TestReports(TestReportsCommon):
delivery = delivery_form.save() delivery = delivery_form.save()
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 5 move_line.product_uom_qty = 5
delivery = delivery_form.save() delivery = delivery_form.save()
delivery.action_reset_draft() delivery.action_reset_draft()
@@ -679,7 +679,7 @@ class TestReports(TestReportsCommon):
delivery = delivery_form.save() delivery = delivery_form.save()
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 5 move_line.product_uom_qty = 5
delivery = delivery_form.save() delivery = delivery_form.save()
delivery.action_reset_draft() delivery.action_reset_draft()
@@ -722,7 +722,7 @@ class TestReports(TestReportsCommon):
delivery_2 = delivery_form.save() delivery_2 = delivery_form.save()
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 8 move_line.product_uom_qty = 8
delivery_2 = delivery_form.save() delivery_2 = delivery_form.save()
delivery_2.action_reset_draft() delivery_2.action_reset_draft()
@@ -777,7 +777,7 @@ class TestReports(TestReportsCommon):
wh_1_receipt = receipt_form.save() wh_1_receipt = receipt_form.save()
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 2 move_line.product_uom_qty = 2
wh_1_receipt = receipt_form.save() wh_1_receipt = receipt_form.save()
wh_1_receipt.action_reset_draft() wh_1_receipt.action_reset_draft()
@@ -790,7 +790,7 @@ class TestReports(TestReportsCommon):
wh_2_receipt = receipt_form.save() wh_2_receipt = receipt_form.save()
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 5 move_line.product_uom_qty = 5
wh_2_receipt = receipt_form.save() wh_2_receipt = receipt_form.save()
wh_2_receipt.action_reset_draft() wh_2_receipt.action_reset_draft()
@@ -951,7 +951,7 @@ class TestReports(TestReportsCommon):
delivery_form.picking_type_id = self.picking_type_out delivery_form.picking_type_id = self.picking_type_out
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 100 move_line.product_uom_qty = 100
delivery = delivery_form.save() delivery = delivery_form.save()
delivery.action_confirm() delivery.action_confirm()
@@ -962,7 +962,7 @@ class TestReports(TestReportsCommon):
delivery_form.picking_type_id = self.picking_type_out delivery_form.picking_type_id = self.picking_type_out
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 200 move_line.product_uom_qty = 200
delivery2 = delivery_form.save() delivery2 = delivery_form.save()
delivery2.action_confirm() delivery2.action_confirm()
@@ -974,7 +974,7 @@ class TestReports(TestReportsCommon):
receipt = receipt_form.save() receipt = receipt_form.save()
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 200 move_line.product_uom_qty = 200
receipt = receipt_form.save() receipt = receipt_form.save()
receipt.move_ids[0].write({ receipt.move_ids[0].write({
'move_dest_ids': [(4, delivery2.move_ids[0].id)], 'move_dest_ids': [(4, delivery2.move_ids[0].id)],
@@ -1011,7 +1011,7 @@ class TestReports(TestReportsCommon):
delivery_form.picking_type_id = self.picking_type_out delivery_form.picking_type_id = self.picking_type_out
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 100 move_line.product_uom_qty = 100
delivery = delivery_form.save() delivery = delivery_form.save()
delivery.action_confirm() delivery.action_confirm()
@@ -1022,7 +1022,7 @@ class TestReports(TestReportsCommon):
delivery_form.picking_type_id = self.picking_type_out delivery_form.picking_type_id = self.picking_type_out
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 200 move_line.product_uom_qty = 200
delivery2 = delivery_form.save() delivery2 = delivery_form.save()
delivery2.action_confirm() delivery2.action_confirm()
@@ -1034,7 +1034,7 @@ class TestReports(TestReportsCommon):
receipt = receipt_form.save() receipt = receipt_form.save()
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 300 move_line.product_uom_qty = 300
receipt = receipt_form.save() receipt = receipt_form.save()
receipt.move_ids[0].write({ receipt.move_ids[0].write({
'move_dest_ids': [(4, delivery2.move_ids[0].id)], 'move_dest_ids': [(4, delivery2.move_ids[0].id)],
@@ -1068,8 +1068,10 @@ class TestReports(TestReportsCommon):
delivery_form.scheduled_date = date.today() delivery_form.scheduled_date = date.today()
with delivery_form.move_ids_without_package.new() as move: with delivery_form.move_ids_without_package.new() as move:
move.product_id = self.product move.product_id = self.product
move.quantity_done = 200 move.product_uom_qty = 200
delivery1 = delivery_form.save() delivery1 = delivery_form.save()
delivery1.action_reset_draft()
delivery1.action_confirm()
# Creation of one receipt with date 'today + 1' and smaller qty than the delivery # Creation of one receipt with date 'today + 1' and smaller qty than the delivery
scheduled_date1 = datetime.now() + timedelta(days=1) scheduled_date1 = datetime.now() + timedelta(days=1)
@@ -1079,8 +1081,10 @@ class TestReports(TestReportsCommon):
receipt_form.scheduled_date = scheduled_date1 receipt_form.scheduled_date = scheduled_date1
with receipt_form.move_ids_without_package.new() as move: with receipt_form.move_ids_without_package.new() as move:
move.product_id = self.product move.product_id = self.product
move.quantity_done = 150 move.product_uom_qty = 150
receipt1 = receipt_form.save() receipt1 = receipt_form.save()
receipt1.action_reset_draft()
receipt1.action_confirm()
self.assertEqual(receipt1.move_ids.forecast_availability, -50.0) self.assertEqual(receipt1.move_ids.forecast_availability, -50.0)
self.assertEqual(delivery1.move_ids.forecast_availability, 150) self.assertEqual(delivery1.move_ids.forecast_availability, 150)
self.assertEqual(delivery1.move_ids.forecast_expected_date, scheduled_date1) self.assertEqual(delivery1.move_ids.forecast_expected_date, scheduled_date1)
@@ -1093,14 +1097,17 @@ class TestReports(TestReportsCommon):
receipt_form.scheduled_date = scheduled_date2 receipt_form.scheduled_date = scheduled_date2
with receipt_form.move_ids_without_package.new() as move: with receipt_form.move_ids_without_package.new() as move:
move.product_id = self.product move.product_id = self.product
move.quantity_done = 150 move.product_uom_qty = 150
receipt2 = receipt_form.save() receipt2 = receipt_form.save()
receipt2.action_reset_draft()
receipt2.action_confirm()
# Check forecast_information of delivery1 # Check forecast_information of delivery1
delivery1.move_ids._compute_forecast_information() # Because depends not "complete" delivery1.move_ids._compute_forecast_information() # Because depends not "complete"
self.assertEqual(delivery1.move_ids.forecast_availability, 200) self.assertEqual(delivery1.move_ids.forecast_availability, 200)
self.assertEqual(delivery1.move_ids.forecast_expected_date, scheduled_date2) self.assertEqual(delivery1.move_ids.forecast_expected_date, scheduled_date2)
receipt2.move_ids.quantity_done = receipt2.move_ids.product_uom_qty
receipt2.button_validate() receipt2.button_validate()
self.assertEqual(receipt1.move_ids.forecast_availability, 100.0) self.assertEqual(receipt1.move_ids.forecast_availability, 100.0)
@@ -1113,6 +1120,8 @@ class TestReports(TestReportsCommon):
delivery2_form = Form(delivery2) delivery2_form = Form(delivery2)
delivery2_form.scheduled_date = datetime.now() + timedelta(days=1) delivery2_form.scheduled_date = datetime.now() + timedelta(days=1)
delivery2 = delivery2_form.save() delivery2 = delivery2_form.save()
delivery2.action_reset_draft()
delivery2.action_confirm()
delivery2.move_ids.quantity_done = delivery1.move_ids.quantity_done delivery2.move_ids.quantity_done = delivery1.move_ids.quantity_done
# To avoid stealing the 150 unit in stock # To avoid stealing the 150 unit in stock
delivery2.do_unreserve() delivery2.do_unreserve()
@@ -1159,8 +1168,10 @@ class TestReports(TestReportsCommon):
delivery_form.scheduled_date = datetime.now() - timedelta(days=10) delivery_form.scheduled_date = datetime.now() - timedelta(days=10)
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 3 move_line.product_uom_qty = 3
delivery_manual = delivery_form.save() delivery_manual = delivery_form.save()
delivery_manual.action_reset_draft()
delivery_manual.action_confirm()
# 'by_date' reservation => reservation_date = 1 day before today # 'by_date' reservation => reservation_date = 1 day before today
delivery_form = Form(self.env['stock.picking'].with_context( delivery_form = Form(self.env['stock.picking'].with_context(
@@ -1171,8 +1182,9 @@ class TestReports(TestReportsCommon):
delivery_form.scheduled_date = datetime.now() + timedelta(days=5) delivery_form.scheduled_date = datetime.now() + timedelta(days=5)
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 3 move_line.product_uom_qty = 3
delivery_by_date = delivery_form.save() delivery_by_date = delivery_form.save()
delivery_by_date.action_reset_draft()
delivery_by_date.action_confirm() delivery_by_date.action_confirm()
# 'by_date' reservation (priority) => reservation_date = 1 day after today # 'by_date' reservation (priority) => reservation_date = 1 day after today
@@ -1184,13 +1196,14 @@ class TestReports(TestReportsCommon):
delivery_form.scheduled_date = datetime.now() + timedelta(days=5) delivery_form.scheduled_date = datetime.now() + timedelta(days=5)
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 3 move_line.product_uom_qty = 3
delivery_by_date_priority = delivery_form.save() delivery_by_date_priority = delivery_form.save()
# <field name="priority" invisible="name == '/'"/> # <field name="priority" invisible="name == '/'"/>
# The priority field is not visible until the name is set, # The priority field is not visible until the name is set,
# which is done after a first save / the `create` # which is done after a first save / the `create`
delivery_form.priority = '1' delivery_form.priority = '1'
delivery_by_date_priority = delivery_form.save() delivery_by_date_priority = delivery_form.save()
delivery_by_date_priority.action_reset_draft()
delivery_by_date_priority.action_confirm() delivery_by_date_priority.action_confirm()
# 'at_confirm' reservation => reservation_date = today # 'at_confirm' reservation => reservation_date = today
@@ -1201,7 +1214,7 @@ class TestReports(TestReportsCommon):
delivery_form.picking_type_id = picking_type_at_confirm delivery_form.picking_type_id = picking_type_at_confirm
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 3 move_line.product_uom_qty = 3
delivery_at_confirm = delivery_form.save() delivery_at_confirm = delivery_form.save()
delivery_at_confirm.action_reset_draft() delivery_at_confirm.action_reset_draft()
delivery_at_confirm.action_confirm() delivery_at_confirm.action_confirm()
@@ -1224,8 +1237,9 @@ class TestReports(TestReportsCommon):
receipt_form.scheduled_date = date.today() + timedelta(days=1) receipt_form.scheduled_date = date.today() + timedelta(days=1)
with receipt_form.move_ids_without_package.new() as move: with receipt_form.move_ids_without_package.new() as move:
move.product_id = self.product move.product_id = self.product
move.quantity_done = 6 move.product_uom_qty = 6
receipt1 = receipt_form.save() receipt1 = receipt_form.save()
receipt1.action_reset_draft()
receipt1.action_confirm() receipt1.action_confirm()
self.assertEqual(all_delivery.move_ids.mapped("forecast_availability"), [3, 3, -3.0, -3.0]) self.assertEqual(all_delivery.move_ids.mapped("forecast_availability"), [3, 3, -3.0, -3.0])
@@ -1246,14 +1260,14 @@ class TestReports(TestReportsCommon):
outgoing.picking_type_id = self.picking_type_out outgoing.picking_type_id = self.picking_type_out
with outgoing.move_ids_without_package.new() as move: with outgoing.move_ids_without_package.new() as move:
move.product_id = self.product move.product_id = self.product
move.quantity_done = 2 move.product_uom_qty = 2
outgoing = outgoing.save() outgoing = outgoing.save()
outgoing.action_confirm() outgoing.action_confirm()
incoming = Form(self.env['stock.picking']) incoming = Form(self.env['stock.picking'])
incoming.picking_type_id = self.picking_type_in incoming.picking_type_id = self.picking_type_in
with incoming.move_ids_without_package.new() as move: with incoming.move_ids_without_package.new() as move:
move.product_id = self.product move.product_id = self.product
move.quantity_done = 2 move.product_uom_qty = 2
incoming = incoming.save() incoming = incoming.save()
incoming.action_confirm() incoming.action_confirm()
incoming.action_set_quantities_to_reservation() incoming.action_set_quantities_to_reservation()
@@ -1289,10 +1303,10 @@ class TestReports(TestReportsCommon):
delivery_form.picking_type_id = self.picking_type_out delivery_form.picking_type_id = self.picking_type_out
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 5 move_line.product_uom_qty = 5
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = product2 move_line.product_id = product2
move_line.quantity_done = 10 move_line.product_uom_qty = 10
delivery1 = delivery_form.save() delivery1 = delivery_form.save()
delivery1.action_confirm() delivery1.action_confirm()
@@ -1303,7 +1317,7 @@ class TestReports(TestReportsCommon):
delivery_form.picking_type_id = self.picking_type_out delivery_form.picking_type_id = self.picking_type_out
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 2 move_line.product_uom_qty = 2
delivery2 = delivery_form.save() delivery2 = delivery_form.save()
delivery2.action_confirm() delivery2.action_confirm()
@@ -1316,15 +1330,15 @@ class TestReports(TestReportsCommon):
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
# incoming qty greater than total (2 moves) outgoing amount => 2 report lines, each = outgoing qty # incoming qty greater than total (2 moves) outgoing amount => 2 report lines, each = outgoing qty
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 15 move_line.product_uom_qty = 15
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
# outgoing qty greater than incoming amount => report line = incoming qty # outgoing qty greater than incoming amount => report line = incoming qty
move_line.product_id = product2 move_line.product_id = product2
move_line.quantity_done = 5 move_line.product_uom_qty = 5
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
# not outgoing => shouldn't appear in report # not outgoing => shouldn't appear in report
move_line.product_id = product3 move_line.product_id = product3
move_line.quantity_done = 5 move_line.product_uom_qty = 5
receipt = receipt_form.save() receipt = receipt_form.save()
receipt.action_reset_draft() receipt.action_reset_draft()
@@ -1396,7 +1410,7 @@ class TestReports(TestReportsCommon):
delivery_form.picking_type_id = self.picking_type_out delivery_form.picking_type_id = self.picking_type_out
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 100 move_line.product_uom_qty = 100
delivery = delivery_form.save() delivery = delivery_form.save()
delivery.action_reset_draft() delivery.action_reset_draft()
delivery.action_confirm() delivery.action_confirm()
@@ -1409,7 +1423,8 @@ class TestReports(TestReportsCommon):
receipt_form.picking_type_id = self.picking_type_in receipt_form.picking_type_id = self.picking_type_in
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 5 move_line.product_uom_qty = 5
receipt1 = receipt_form.save()
receipt1 = receipt_form.save() receipt1 = receipt_form.save()
receipt1.action_reset_draft() receipt1.action_reset_draft()
@@ -1420,7 +1435,7 @@ class TestReports(TestReportsCommon):
receipt_form.picking_type_id = self.picking_type_in receipt_form.picking_type_id = self.picking_type_in
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 3 move_line.product_uom_qty = 3
receipt2 = receipt_form.save() receipt2 = receipt_form.save()
receipt2.action_reset_draft() receipt2.action_reset_draft()
@@ -1477,7 +1492,7 @@ class TestReports(TestReportsCommon):
delivery_form.picking_type_id = picking_type_out_2 delivery_form.picking_type_id = picking_type_out_2
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 100 move_line.product_uom_qty = 100
delivery = delivery_form.save() delivery = delivery_form.save()
delivery.action_confirm() delivery.action_confirm()
@@ -1532,8 +1547,9 @@ class TestReports(TestReportsCommon):
receipt_form.picking_type_id = self.picking_type_in receipt_form.picking_type_id = self.picking_type_in
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = 15 move_line.product_uom_qty = 15
receipt = receipt_form.save() receipt = receipt_form.save()
receipt.action_reset_draft()
report = self.env['report.stock.report_reception'] report = self.env['report.stock.report_reception']
report_values = report._get_report_values(docids=[receipt.id]) report_values = report._get_report_values(docids=[receipt.id])
@@ -1560,8 +1576,9 @@ class TestReports(TestReportsCommon):
delivery_form.picking_type_id = self.picking_type_out delivery_form.picking_type_id = self.picking_type_out
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = outgoing_qty move_line.product_uom_qty = outgoing_qty
delivery = delivery_form.save() delivery = delivery_form.save()
delivery.action_reset_draft()
delivery.action_confirm() delivery.action_confirm()
receipt_form = Form(self.env['stock.picking'].with_context( receipt_form = Form(self.env['stock.picking'].with_context(
@@ -1571,8 +1588,9 @@ class TestReports(TestReportsCommon):
receipt_form.picking_type_id = self.picking_type_in receipt_form.picking_type_id = self.picking_type_in
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = incoming_qty move_line.product_uom_qty = incoming_qty
receipt = receipt_form.save() receipt = receipt_form.save()
receipt.action_reset_draft()
receipt.action_confirm() receipt.action_confirm()
self.assertEqual(len(delivery.move_ids_without_package), 1) self.assertEqual(len(delivery.move_ids_without_package), 1)
@@ -1626,7 +1644,7 @@ class TestReports(TestReportsCommon):
delivery_form.picking_type_id = self.picking_type_out delivery_form.picking_type_id = self.picking_type_out
with delivery_form.move_ids_without_package.new() as move_line: with delivery_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = outgoing_qty move_line.product_uom_qty = outgoing_qty
delivery = delivery_form.save() delivery = delivery_form.save()
delivery.action_confirm() delivery.action_confirm()
@@ -1638,7 +1656,7 @@ class TestReports(TestReportsCommon):
receipt_form.picking_type_id = self.picking_type_in receipt_form.picking_type_id = self.picking_type_in
with receipt_form.move_ids_without_package.new() as move_line: with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.product move_line.product_id = self.product
move_line.quantity_done = incoming_qty move_line.product_uom_qty = incoming_qty
receipt = receipt_form.save() receipt = receipt_form.save()
receipt.action_reset_draft() receipt.action_reset_draft()
receipt.action_confirm() receipt.action_confirm()
+7 -6
View File
@@ -1927,10 +1927,10 @@ class TestStockFlow(TestStockCommon):
f.picking_type_id = warehouse_company_1.out_type_id f.picking_type_id = warehouse_company_1.out_type_id
with f.move_ids_without_package.new() as move: with f.move_ids_without_package.new() as move:
move.product_id = product_from_company_2 move.product_id = product_from_company_2
move.quantity_done = 5 move.product_uom_qty = 5
with f.move_ids_without_package.new() as move: with f.move_ids_without_package.new() as move:
move.product_id = product_from_company_3 move.product_id = product_from_company_3
move.quantity_done = 5 move.product_uom_qty = 5
picking = f.save() picking = f.save()
picking.action_reset_draft() picking.action_reset_draft()
@@ -2024,11 +2024,12 @@ class TestStockFlow(TestStockCommon):
picking_form.picking_type_id = self.env.ref('stock.picking_type_in') picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
self.assertFalse(move._get_modifier('quantity_done', 'column_invisible')) self.assertTrue(move._get_modifier('quantity_done', 'column_invisible'))
self.assertFalse(move._get_modifier('product_uom_qty', 'column_invisible'))
self.assertTrue(move._get_modifier('forecast_availability', 'column_invisible')) self.assertTrue(move._get_modifier('forecast_availability', 'column_invisible'))
self.assertTrue(move._get_modifier('reserved_availability', 'column_invisible')) self.assertTrue(move._get_modifier('reserved_availability', 'column_invisible'))
move.product_id = self.productA move.product_id = self.productA
move.quantity_done = 1 move.product_uom_qty = 1
picking = picking_form.save() picking = picking_form.save()
self.assertEqual(picking.state, 'assigned') self.assertEqual(picking.state, 'assigned')
@@ -2067,7 +2068,7 @@ class TestStockFlow(TestStockCommon):
picking_form.picking_type_id = picking_type picking_form.picking_type_id = picking_type
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = product_lot move.product_id = product_lot
move.quantity_done = 8 move.product_uom_qty = 8
receipt_1 = picking_form.save() receipt_1 = picking_form.save()
receipt_1.action_reset_draft() receipt_1.action_reset_draft()
receipt_1.action_confirm() receipt_1.action_confirm()
@@ -2088,7 +2089,7 @@ class TestStockFlow(TestStockCommon):
picking_form.picking_type_id = picking_type picking_form.picking_type_id = picking_type
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = product_lot move.product_id = product_lot
move.quantity_done = 8 move.product_uom_qty = 8
receipt_2 = picking_form.save() receipt_2 = picking_form.save()
receipt_2.action_reset_draft() receipt_2.action_reset_draft()
receipt_2.action_confirm() receipt_2.action_confirm()
+77 -32
View File
@@ -119,9 +119,6 @@
<div class="o_kanban_record_headings"> <div class="o_kanban_record_headings">
<strong class="o_kanban_record_title"><span><field name="product_id" readonly="state == 'done'"/></span></strong> <strong class="o_kanban_record_title"><span><field name="product_id" readonly="state == 'done'"/></span></strong>
</div> </div>
<button name="action_show_details" string="Register lots, packs, location"
class="o_icon_button fa fa-list" type="object"
invisible="not show_details_visible"/>
</div> </div>
<div class="o_kanban_record_body"> <div class="o_kanban_record_body">
<div invisible="not is_inventory"> <div invisible="not is_inventory">
@@ -158,42 +155,87 @@
<field name="display_import_lot" invisible="1"/> <field name="display_import_lot" invisible="1"/>
<field name="from_immediate_transfer" invisible="1"/> <field name="from_immediate_transfer" invisible="1"/>
<field name="product_uom_category_id" invisible="1"/> <field name="product_uom_category_id" invisible="1"/>
<field name="display_clear_serial" invisible="1"/> <field name="product_uom" invisible="1" groups="!uom.group_uom"/>
<field name="picking_code" invisible="1"/> <field name="picking_code" invisible="1"/>
<field name="has_tracking" invisible="1"/>
<field name="show_quant" invisible="1"/>
<field name="show_lots_text" invisible="1"/>
<field name="show_lots_m2o" invisible="1"/>
<group> <group>
<group> <group>
<field name="product_id" readonly="1"/> <field name="product_id" readonly="1"/>
<label for="product_uom_qty" invisible="from_immediate_transfer"/> <label for="product_uom_qty"/>
<div class="o_row" invisible="from_immediate_transfer"> <div class="o_row">
<span><field name="product_uom_qty" readonly="1" nolabel="1"/></span> <span><field name="product_uom_qty" readonly="1" nolabel="1"/></span>
<span><field name="product_uom" readonly="1" nolabel="1" options="{'no_open': True}"/></span> <span><field name="product_uom" groups="uom.group_uom" readonly="1" nolabel="1" options="{'no_open': True}"/></span>
</div> </div>
<label for="quantity_done"/> <label for="quantity_done"/>
<div class="o_row"> <div class="o_row">
<span><field name="quantity_done" readonly="1" nolabel="1"/></span> <span><field name="quantity_done" readonly="1" nolabel="1"/></span>
<span invisible="state == 'done' or from_immediate_transfer"> / </span> <span invisible="state == 'done'"> / </span>
<span><field name="reserved_availability" nolabel="1" invisible="state == 'done' or from_immediate_transfer" /></span> <span><field name="reserved_availability" nolabel="1" invisible="state == 'done'" /></span>
<span><field name="product_uom" nolabel="1" readonly="1" invisible="from_immediate_transfer" options="{'no_open': True}"/></span> <span><field name="product_uom" groups="uom.group_uom" readonly="1" nolabel="1" options="{'no_open': True}"/></span>
</div>
<div class="o_row">
<button name="action_open_generate_serial" invisible="not display_assign_serial" string="Generate Serials" type="object" class="float-end btn-link"/>
<button name="action_open_import_lot" invisible="not display_import_lot" string="Import Serials/Lots" type="object" class="float-end btn-link"/>
</div> </div>
</group> </group>
</group> </group>
<field name="move_line_ids" <field name="move_line_ids"
readonly="state == 'cancel' or (state == 'done' and is_locked)" readonly="state == 'cancel' or (state == 'done' and is_locked)"
context="{'tree_view_ref': 'stock.view_stock_move_line_operation_tree', 'default_product_uom_id': product_uom, 'default_picking_id': picking_id, 'default_move_id': id, 'default_product_id': product_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_company_id': company_id}" context="{'tree_view_ref': 'stock.view_stock_move_line_operation_tree', 'default_picking_id': picking_id, 'default_move_id': id, 'default_product_id': product_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_company_id': company_id}"
widget="sml_x2_many"/>
</form>
</field>
</record>
<record id="view_stock_move_operations_immediate" model="ir.ui.view">
<field name="name">stock.move.operations.form.immediate</field>
<field name="model">stock.move</field>
<field name="priority">1000</field>
<field name="arch" type="xml">
<form string="Move Detail immediate">
<field name="sequence" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="state" invisible="1"/>
<field name="location_id" invisible="1"/>
<field name="location_dest_id" invisible="1"/>
<field name="picking_id" invisible="1"/>
<field name="picking_type_id" invisible="1"/>
<field name="is_locked" invisible="1"/>
<field name="picking_type_entire_packs" invisible="1"/>
<field name="display_assign_serial" invisible="1"/>
<field name="display_import_lot" invisible="1"/>
<field name="from_immediate_transfer" invisible="1"/>
<field name="product_uom_category_id" invisible="1"/>
<field name="picking_code" invisible="1"/>
<field name="product_uom" invisible="1" groups="!uom.group_uom"/>
<field name="show_reserved" invisible="1"/>
<field name="show_quant" invisible="1"/>
<field name="show_lots_text" invisible="1"/>
<field name="show_lots_m2o" invisible="1"/>
<group>
<group>
<field name="product_id" readonly="1"/>
<label for="product_uom_qty" string="Done"/>
<div class="o_row d-flex">
<span><field name="product_uom_qty" nolabel="1"/></span>
<span><field name="product_uom" groups="uom.group_uom" readonly="1" nolabel="1" options="{'no_open': True}"/></span>
(<span ><field name="quantity_done" readonly="1" decoration-danger="quantity_done &gt; product_uom_qty" nolabel="1"/></span>
<span><field name="product_uom" groups="uom.group_uom" readonly="1" decoration-danger="quantity_done &gt; product_uom_qty" nolabel="1" options="{'no_open': True}"/></span> reserved)
</div>
</group>
</group>
<field name="move_line_nosuggest_ids"
readonly="state == 'cancel' or (state == 'done' and is_locked)"
invisible="show_reserved"
context="{'tree_view_ref': 'stock.view_stock_move_line_operation_tree', 'default_picking_id': picking_id, 'default_move_id': id, 'default_product_id': product_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_company_id': company_id}"
widget="sml_x2_many"/>
<field name="move_line_ids"
readonly="state == 'cancel' or (state == 'done' and is_locked)"
invisible="not show_reserved"
context="{'tree_view_ref': 'stock.view_stock_move_line_operation_tree', 'default_picking_id': picking_id, 'default_move_id': id, 'default_product_id': product_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_company_id': company_id}"
widget="sml_x2_many"/> widget="sml_x2_many"/>
<footer class="d-flex justify-content-between" invisible="state == 'cancel' or state == 'done' and is_locked">
<div class="d-flex gap-1">
<button string="Confirm" special="save" data-hotkey="q" class="oe_highlight"/>
<button string="Discard" special="cancel" data-hotkey="x"/>
</div>
<div>
<button name="action_open_generate_serial" invisible="not display_assign_serial" string="Generate Serials" type="object" class="float-end btn-link"/>
<button name="action_open_import_lot" invisible="not display_import_lot" string="Import Serials/Lots" type="object" class="float-end btn-link"/>
</div>
</footer>
<footer class="oe_edit_only" invisible="state != 'cancel' and state != 'done' or state == 'done' and not is_locked">
<button string="Close" special="cancel" data-hotkey="x"/>
</footer>
</form> </form>
</field> </field>
</record> </record>
@@ -226,19 +268,22 @@
<field name="location_id" column_invisible="True"/> <field name="location_id" column_invisible="True"/>
<field name="location_dest_id" column_invisible="True"/> <field name="location_dest_id" column_invisible="True"/>
<field name="package_id" column_invisible="True"/> <field name="package_id" column_invisible="True"/>
<field name="tracking" column_invisible="True"/>
<field name="picking_type_id" column_invisible="True"/>
<field name="product_uom_id" column_invisible="True" groups="!uom.group_uom"/>
<field name="quant_id" <field name="quant_id"
domain="[('product_id', '=', product_id), ('location_id', 'child_of', parent.location_id)]" domain="[('product_id', '=', product_id), ('location_id', 'child_of', parent.location_id)]"
context="{'default_location_id': location_id, 'default_product_id': product_id, 'search_view_ref': 'stock.quant_search_view', 'tree_view_ref': 'stock.view_stock_quant_tree', 'form_view_ref': 'stock.view_stock_quant_form', 'readonly_form': True}" context="{'default_location_id': location_id, 'default_product_id': product_id, 'search_view_ref': 'stock.quant_search_view', 'tree_view_ref': 'stock.view_stock_quant_tree', 'form_view_ref': 'stock.view_stock_quant_form', 'readonly_form': True}"
widget="pick_from" widget="pick_from"
column_invisible="not context.get('show_quant')" column_invisible="not parent.show_quant"
options="{'no_create': True, 'no_open': True}"/> options="{'no_create': True, 'no_open': True}"/>
<field name="location_dest_id" <field name="location_dest_id"
column_invisible="not context.get('show_destination_location')" column_invisible="parent.show_quant and parent.picking_code != 'internal'"
readonly="package_level_id and parent.picking_type_entire_packs" readonly="package_level_id and parent.picking_type_entire_packs"
domain="[('id', 'child_of', parent.location_dest_id), '|', ('company_id', '=', False), ('company_id', '=', company_id), ('usage', '!=', 'view')]" domain="[('id', 'child_of', parent.location_dest_id), '|', ('company_id', '=', False), ('company_id', '=', company_id), ('usage', '!=', 'view')]"
groups="stock.group_stock_multi_locations"/> groups="stock.group_stock_multi_locations"/>
<field name="lot_id" groups="stock.group_production_lot" <field name="lot_id" groups="stock.group_production_lot"
column_invisible="not context.get('show_lots_m2o')" column_invisible="parent.has_tracking == 'none' or not parent.show_lots_m2o"
readonly="package_level_id and parent.picking_type_entire_packs" readonly="package_level_id and parent.picking_type_entire_packs"
domain="[('product_id', '=', parent.product_id), ('company_id', '=', company_id)]" domain="[('product_id', '=', parent.product_id), ('company_id', '=', company_id)]"
context="{ context="{
@@ -246,14 +291,14 @@
'default_company_id': parent.company_id, 'default_company_id': parent.company_id,
'default_product_id': parent.product_id, 'default_product_id': parent.product_id,
}"/> }"/>
<field name="lot_name" string="Lot/Serial Number" widget="text" groups="stock.group_production_lot" <field name="lot_name" string="Lot/Serial Number" groups="stock.group_production_lot"
placeholder="Write your SN/LN one by one or copy paste a list." placeholder="Write your SN/LN one by one or copy paste a list."
column_invisible="not context.get('show_lots_text')" column_invisible="parent.has_tracking == 'none' or not parent.show_lots_text"
readonly="package_level_id and parent.picking_type_entire_packs"/> readonly="package_level_id and parent.picking_type_entire_packs"/>
<field name="package_id" column_invisible="not context.get('show_package')" readonly="package_level_id and parent.picking_type_entire_packs" groups="stock.group_tracking_lot"/> <field name="package_id" column_invisible="parent.show_quant" readonly="package_level_id and parent.picking_type_entire_packs" groups="stock.group_tracking_lot"/>
<field name="result_package_id" readonly="package_level_id and parent.picking_type_entire_packs" groups="stock.group_tracking_lot" context="{'picking_id': picking_id}"/> <field name="result_package_id" readonly="package_level_id and parent.picking_type_entire_packs" groups="stock.group_tracking_lot" context="{'picking_id': picking_id}"/>
<field name="owner_id" column_invisible="not context.get('show_owner')" readonly="package_level_id and parent.picking_type_entire_packs" groups="stock.group_tracking_owner"/> <field name="owner_id" column_invisible="parent.show_quant" readonly="package_level_id and parent.picking_type_entire_packs" groups="stock.group_tracking_owner"/>
<field name="reserved_uom_qty" column_invisible="not context.get('show_reserved_quantity')"/> <field name="reserved_uom_qty" column_invisible="parent.from_immediate_transfer or not parent.show_quant or parent.state == 'done'"/>
<field name="state" column_invisible="True"/> <field name="state" column_invisible="True"/>
<field name="is_locked" column_invisible="True"/> <field name="is_locked" column_invisible="True"/>
<field name="picking_code" column_invisible="True"/> <field name="picking_code" column_invisible="True"/>
+16 -30
View File
@@ -265,13 +265,14 @@
<field name="move_ids_without_package" mode="tree,kanban" <field name="move_ids_without_package" mode="tree,kanban"
widget="stock_move_one2many" widget="stock_move_one2many"
readonly="state == 'done' and is_locked" readonly="state == 'done' and is_locked"
context="{'default_company_id': company_id, 'default_date': scheduled_date, 'default_date_deadline': date_deadline, 'picking_type_code': picking_type_code, 'default_picking_id': id, 'form_view_ref':'stock.view_move_form', 'address_in_id': partner_id, 'default_picking_type_id': picking_type_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_partner_id': partner_id}" context="{'default_company_id': company_id, 'default_date': scheduled_date, 'default_date_deadline': date_deadline, 'picking_type_code': picking_type_code, 'default_picking_id': id, 'form_view_ref': immediate_transfer and 'stock.view_stock_move_operations_immediate' or 'stock.view_stock_move_operations', 'address_in_id': partner_id, 'default_picking_type_id': picking_type_id, 'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_partner_id': partner_id}"
add-label="Add a Product"> add-label="Add a Product">
<tree decoration-danger="state != 'done' and quantity_done > reserved_availability and show_reserved_availability" decoration-muted="scrapped == True or state == 'cancel' or (state == 'done' and is_locked == True)" string="Stock Moves" editable="bottom"> <tree decoration-danger="state != 'done' and quantity_done > reserved_availability and show_reserved_availability" decoration-muted="scrapped == True or state == 'cancel' or (state == 'done' and is_locked == True)" string="Stock Moves" editable="bottom">
<field name="company_id" column_invisible="True"/> <field name="company_id" column_invisible="True"/>
<field name="name" column_invisible="True"/> <field name="name" column_invisible="True"/>
<field name="state" readonly="0" column_invisible="True"/> <field name="state" readonly="0" column_invisible="True"/>
<field name="picking_type_id" column_invisible="True"/> <field name="picking_type_id" column_invisible="True"/>
<field name="move_line_ids" column_invisible="True"/>
<field name="location_id" column_invisible="True"/> <field name="location_id" column_invisible="True"/>
<field name="location_dest_id" column_invisible="True"/> <field name="location_dest_id" column_invisible="True"/>
<field name="partner_id" column_invisible="True" readonly="state == 'done'"/> <field name="partner_id" column_invisible="True" readonly="state == 'done'"/>
@@ -281,22 +282,30 @@
<field name="show_details_visible" column_invisible="True"/> <field name="show_details_visible" column_invisible="True"/>
<field name="show_reserved_availability" column_invisible="True"/> <field name="show_reserved_availability" column_invisible="True"/>
<field name="show_operations" readonly="1" column_invisible="True"/> <field name="show_operations" readonly="1" column_invisible="True"/>
<field name="show_reserved" column_invisible="True"/>
<field name="additional" column_invisible="True"/> <field name="additional" column_invisible="True"/>
<field name="move_lines_count" column_invisible="True"/> <field name="move_lines_count" column_invisible="True"/>
<field name="is_locked" column_invisible="True"/> <field name="is_locked" column_invisible="True"/>
<field name="product_uom_category_id" column_invisible="True"/> <field name="product_uom_category_id" column_invisible="True"/>
<field name="has_tracking" column_invisible="True"/> <field name="has_tracking" column_invisible="True"/>
<field name="display_assign_serial" column_invisible="True"/> <field name="display_assign_serial" column_invisible="True"/>
<field name="product_id" context="{'default_detailed_type': 'product'}" required="1" readonly="(state != 'draft' and not additional) or move_lines_count &gt; 0"/> <field name="product_id" context="{'default_detailed_type': 'product'}" required="1" readonly="(state != 'draft' and not additional) or move_lines_count &gt; 0" force_save="1"/>
<field name="description_picking" string="Description" optional="hide"/> <field name="description_picking" string="Description" optional="hide"/>
<field name="date" optional="hide"/> <field name="date" optional="hide"/>
<field name="date_deadline" optional="hide"/> <field name="date_deadline" optional="hide"/>
<field name="is_initial_demand_editable" column_invisible="True"/> <field name="is_initial_demand_editable" column_invisible="True"/>
<field name="is_quantity_done_editable" column_invisible="True"/> <field name="is_quantity_done_editable" column_invisible="True"/>
<field name="show_quant" column_invisible="True"/>
<field name="show_lots_text" column_invisible="True"/>
<field name="show_lots_m2o" column_invisible="True"/>
<field name="display_assign_serial" column_invisible="True"/>
<field name="display_import_lot" column_invisible="True"/>
<field name="from_immediate_transfer" column_invisible="True"/>
<field name="product_packaging_id" groups="product.group_stock_packaging" <field name="product_packaging_id" groups="product.group_stock_packaging"
context="{'default_product_id': product_id}" context="{'default_product_id': product_id}"
readonly="not product_id"/> readonly="not product_id"/>
<field name="product_uom_qty" string="Demand" column_invisible="parent.immediate_transfer" readonly="state != 'draft' and (not is_initial_demand_editable or (show_operations and is_locked and not is_initial_demand_editable))"/> <field name="product_uom_qty" string="Demand" column_invisible="parent.immediate_transfer" readonly="not is_initial_demand_editable"/>
<field name="product_uom_qty" string="Done" column_invisible="not parent.immediate_transfer or parent.state == 'done'" readonly="state != 'draft' and (not is_initial_demand_editable or (show_operations and is_locked and not is_initial_demand_editable))"/>
<button type="object" name="action_product_forecast_report" title="Forecast Report" icon="fa-area-chart" <button type="object" name="action_product_forecast_report" title="Forecast Report" icon="fa-area-chart"
invisible="forecast_availability &lt; 0 or parent.immediate_transfer or (parent.picking_type_code == 'outgoing' and state != 'draft')"/> invisible="forecast_availability &lt; 0 or parent.immediate_transfer or (parent.picking_type_code == 'outgoing' and state != 'draft')"/>
<button type="object" name="action_product_forecast_report" title="Forecast Report" icon="fa-area-chart text-danger" <button type="object" name="action_product_forecast_report" title="Forecast Report" icon="fa-area-chart text-danger"
@@ -307,45 +316,22 @@
<field name="reserved_availability" string="Reserved" <field name="reserved_availability" string="Reserved"
column_invisible="parent.state in ['draft', 'done'] or parent.picking_type_code in ['incoming', 'outgoing'] or parent.immediate_transfer"/> column_invisible="parent.state in ['draft', 'done'] or parent.picking_type_code in ['incoming', 'outgoing'] or parent.immediate_transfer"/>
<field name="product_qty" readonly="1" column_invisible="True"/> <field name="product_qty" readonly="1" column_invisible="True"/>
<field name="quantity_done" string="Done" column_invisible="parent.state == 'draft' and not parent.immediate_transfer" readonly="not is_quantity_done_editable" <field name="quantity_done" string="Done" column_invisible="parent.state == 'draft' or (parent.immediate_transfer and parent.state != 'done')" readonly="not is_quantity_done_editable" decoration-danger="quantity_done > product_uom_qty"/>
decoration-danger="quantity_done > product_uom_qty"/>
<field name="product_uom" readonly="state != 'draft' and not additional" options="{'no_open': True, 'no_create': True}" string="Unit" groups="uom.group_uom"/> <field name="product_uom" readonly="state != 'draft' and not additional" options="{'no_open': True, 'no_create': True}" string="Unit" groups="uom.group_uom"/>
<field name="product_uom" groups="!uom.group_uom" column_invisible="True"/>
<field name="lot_ids" widget="many2many_tags" <field name="lot_ids" widget="many2many_tags"
groups="stock.group_production_lot" groups="stock.group_production_lot"
invisible="not show_details_visible or has_tracking != 'serial'" invisible="not show_details_visible or has_tracking != 'serial'"
optional="hide" optional="hide"
options="{'create': [('parent.use_create_lots', '=', True)]}" options="{'create': [('parent.use_create_lots', '=', True)]}"
context="{'default_company_id': company_id, 'default_product_id': product_id, 'active_picking_id': parent.id}" context="{'default_company_id': company_id, 'default_product_id': product_id, 'active_picking_id': parent.id}"
domain="[('product_id','=',product_id)]"/> domain="[('product_id','=',product_id)]"
<button name="action_show_details" type="object" icon="fa-list" title="Details" />
invisible="not show_details_visible"/>
<button name="action_assign_serial" type="object" <button name="action_assign_serial" type="object"
icon="fa-plus-square" icon="fa-plus-square"
role="img" title="Assign Serial Numbers" role="img" title="Assign Serial Numbers"
invisible="not display_assign_serial or not show_operations"/> invisible="not display_assign_serial or not show_operations"/>
</tree> </tree>
<form string="Stock Moves">
<header>
<field name="state" widget="statusbar"/>
</header>
<group>
<field name="product_uom_category_id" invisible="1"/>
<field name="additional" invisible="1"/>
<field name="move_lines_count" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="product_id" required="1" readonly="(state != 'draft' and not additional) or move_lines_count &gt; 0"/>
<field name="is_initial_demand_editable" invisible="1"/>
<field name="is_quantity_done_editable" invisible="1"/>
<field name="product_uom_qty" invisible="product_uom_qty == 0" readonly="not is_initial_demand_editable"/>
<field name="reserved_availability" string="Reserved" invisible="parent.state == 'done' or parent.picking_type_code in ['outgoing', 'incoming'] or parent.state == 'assigned'"/>
<field name="product_qty" invisible="1" readonly="1"/>
<field name="forecast_expected_date" invisible="1"/>
<field name="forecast_availability" string="Reserved" invisible="parent.picking_type_code != 'outgoing' or parent.state == 'done'" widget="forecast_widget"/>
<field name="quantity_done" string="Done" readonly="not is_quantity_done_editable"/>
<field name="product_uom" readonly="state != 'draft' and id" options="{'no_open': True, 'no_create': True}" string="Unit of Measure" groups="uom.group_uom"/>
<field name="description_picking" string="Description"/>
</group>
</form>
</field> </field>
<field name="id" invisible="1"/> <field name="id" invisible="1"/>
<field name="package_level_ids" context="{'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_company_id': company_id}" invisible="not picking_type_entire_packs or show_operations" readonly="state == 'done'" /> <field name="package_level_ids" context="{'default_location_id': location_id, 'default_location_dest_id': location_dest_id, 'default_company_id': company_id}" invisible="not picking_type_entire_packs or show_operations" readonly="state == 'done'" />
@@ -375,9 +375,11 @@ class TestBatchPicking(TransactionCase):
]) ])
self.assertFalse(picking.batch_id) self.assertFalse(picking.batch_id)
self.assertEqual(ml1.picking_id.batch_id.id, wave.id) self.assertEqual(ml1.picking_id.batch_id.id, wave.id)
self.assertEqual(ml1.picking_id.move_ids.quantity_done, 5)
self.assertEqual(ml1.picking_id.move_ids.product_uom_qty, 5) self.assertEqual(ml1.picking_id.move_ids.product_uom_qty, 5)
self.assertEqual(ml2.picking_id.id, picking.id) self.assertEqual(ml2.picking_id.id, picking.id)
self.assertEqual(ml2.picking_id.move_ids.product_uom_qty, 10) self.assertEqual(ml2.picking_id.move_ids.quantity_done, 10)
self.assertEqual(ml2.picking_id.move_ids.product_uom_qty, 0)
def test_wave_trigger_errors(self): def test_wave_trigger_errors(self):
with self.assertRaises(UserError): with self.assertRaises(UserError):