[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:
@@ -361,7 +361,7 @@ class StockMove(models.Model):
|
||||
if not bom:
|
||||
moves_ids_to_return.add(move.id)
|
||||
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
|
||||
else:
|
||||
factor = move.product_uom._compute_quantity(move.product_uom_qty, bom.product_uom_id) / bom.product_qty
|
||||
|
||||
@@ -308,7 +308,7 @@ class TestKitPicking(common.TestMrpCommon):
|
||||
move_receipt_1 = self.env['stock.move'].create({
|
||||
'name': self.kit_parent.name,
|
||||
'product_id': self.kit_parent.id,
|
||||
'quantity_done': 3,
|
||||
'product_uom_qty': 3,
|
||||
'product_uom': self.kit_parent.uom_id.id,
|
||||
'picking_id': picking.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}"
|
||||
domain="[('product_id','=',product_id)]"/>
|
||||
<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>
|
||||
</field>
|
||||
</page>
|
||||
@@ -437,8 +435,6 @@
|
||||
options="{'create': [('parent.use_create_components_lots', '!=', False)]}"
|
||||
context="{'default_company_id': company_id, 'default_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>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
@@ -38,8 +38,10 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
picking_form.partner_id = self.subcontractor_partner1
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.finished
|
||||
move.quantity_done = 1
|
||||
move.product_uom_qty = 1
|
||||
picking_receipt = picking_form.save()
|
||||
picking_receipt.action_reset_draft()
|
||||
picking_receipt.action_confirm()
|
||||
|
||||
# Nothing should be tracked
|
||||
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
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.finished
|
||||
move.quantity_done = 1
|
||||
move.product_uom_qty = 1
|
||||
picking_receipt = picking_form.save()
|
||||
picking_receipt.action_reset_draft()
|
||||
picking_receipt.action_confirm()
|
||||
|
||||
picking_receipt.move_ids.quantity_done = 3.0
|
||||
picking_receipt._action_done()
|
||||
@@ -405,7 +409,7 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
picking_form.partner_id = self.subcontractor_partner1
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.finished
|
||||
move.quantity_done = 5
|
||||
move.product_uom_qty = 5
|
||||
picking_receipt = picking_form.save()
|
||||
picking_receipt.action_reset_draft()
|
||||
picking_receipt.action_confirm()
|
||||
@@ -478,8 +482,11 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
picking_form.partner_id = subcontractor_contact
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.finished
|
||||
move.quantity_done = 1
|
||||
move.product_uom_qty = 1
|
||||
picking_receipt = picking_form.save()
|
||||
picking_receipt.action_reset_draft()
|
||||
picking_receipt.action_confirm()
|
||||
|
||||
# Check that a manufacturing order is created
|
||||
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
|
||||
self.assertEqual(len(mo), 1)
|
||||
@@ -493,8 +500,10 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
picking_form.partner_id = self.subcontractor_partner1
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.finished
|
||||
move.quantity_done = 1
|
||||
move.product_uom_qty = 1
|
||||
picking_receipt = picking_form.save()
|
||||
picking_receipt.action_reset_draft()
|
||||
picking_receipt.action_confirm()
|
||||
self.assertEqual(picking_receipt.display_action_record_components, 'facultative')
|
||||
action = picking_receipt.action_record_components()
|
||||
mo = self.env['mrp.production'].browse(action['res_id'])
|
||||
@@ -525,8 +534,10 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
picking_form.partner_id = self.subcontractor_partner1
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.finished
|
||||
move.quantity_done = 1
|
||||
move.product_uom_qty = 1
|
||||
picking_receipt = picking_form.save()
|
||||
picking_receipt.action_reset_draft()
|
||||
picking_receipt.action_confirm()
|
||||
|
||||
self.assertEqual(picking_receipt.display_action_record_components, 'facultative')
|
||||
action = picking_receipt.action_record_components()
|
||||
@@ -632,7 +643,7 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
picking_form.partner_id = self.subcontractor_partner1
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = finished
|
||||
move.quantity_done = 5
|
||||
move.product_uom_qty = 5
|
||||
picking = picking_form.save()
|
||||
picking.action_reset_draft()
|
||||
picking.action_confirm()
|
||||
@@ -1022,7 +1033,7 @@ class TestSubcontractingTracking(TransactionCase):
|
||||
picking_form.partner_id = self.subcontractor_partner1
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = finished_product
|
||||
move.quantity_done = todo_nb
|
||||
move.product_uom_qty = todo_nb
|
||||
picking_receipt = picking_form.save()
|
||||
picking_receipt.action_reset_draft()
|
||||
picking_receipt.action_confirm()
|
||||
@@ -1197,8 +1208,10 @@ class TestSubcontractingPortal(TransactionCase):
|
||||
picking_form.partner_id = self.subcontractor_partner1
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.finished_product
|
||||
move.quantity_done = 2
|
||||
move.product_uom_qty = 2
|
||||
picking_receipt = picking_form.save()
|
||||
picking_receipt.action_reset_draft()
|
||||
picking_receipt.action_confirm()
|
||||
|
||||
# Using the subcontractor (portal user)
|
||||
lot1 = self.env['stock.lot'].with_user(self.portal_user).create({
|
||||
@@ -1309,7 +1322,7 @@ class TestSubcontractingSerialMassReceipt(TransactionCase):
|
||||
picking_form.partner_id = self.subcontractor
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.finished
|
||||
move.quantity_done = sum(quantities)
|
||||
move.product_uom_qty = sum(quantities)
|
||||
picking_receipt = picking_form.save()
|
||||
picking_receipt.action_reset_draft()
|
||||
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" invisible="display_action_record_components != 'facultative'" string="Record components" type="object" data-hotkey="shift+x"/>
|
||||
</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"/>
|
||||
<button name="action_show_subcontract_details" string="Register components for subcontracted product" type="object" icon="fa-sitemap"
|
||||
invisible="not show_subcontracting_details_visible"/>
|
||||
|
||||
@@ -305,7 +305,7 @@ class TestAccountSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
receipt_form.partner_id = self.subcontractor_partner1
|
||||
with receipt_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.finished
|
||||
move.quantity_done = 10
|
||||
move.product_uom_qty = 10
|
||||
receipt = receipt_form.save()
|
||||
receipt.action_reset_draft()
|
||||
# add an extra cost
|
||||
|
||||
@@ -11,6 +11,17 @@
|
||||
</xpath>
|
||||
</field>
|
||||
</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">
|
||||
<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')
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.product_01
|
||||
move.quantity_done = 10.0
|
||||
move.product_uom_qty = 10.0
|
||||
customer_picking = picking_form.save()
|
||||
# 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')
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
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:
|
||||
move.product_id = self.product_01
|
||||
move.quantity_done = 10.0
|
||||
move.product_uom_qty = 10.0
|
||||
customer_picking = picking_form.save()
|
||||
customer_picking.move_ids[0].location_id = subloc_1.id
|
||||
customer_picking.move_ids[1].location_id = subloc_2.id
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
'stock/static/src/scss/stock_traceability_report.scss',
|
||||
],
|
||||
'web.assets_tests': [
|
||||
'stock/static/tests/tours/stock_report_tests.js',
|
||||
'stock/static/tests/tours/*.js',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'stock/static/tests/inventory_report_list_tests.js',
|
||||
|
||||
@@ -132,7 +132,7 @@ class StockMove(models.Model):
|
||||
'Propagate cancel and split', default=True,
|
||||
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)
|
||||
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')
|
||||
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)])
|
||||
@@ -173,7 +173,6 @@ class StockMove(models.Model):
|
||||
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_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_count = fields.Integer('Number of SN')
|
||||
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)
|
||||
from_immediate_transfer = fields.Boolean(related="picking_id.immediate_transfer")
|
||||
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')
|
||||
def _compute_product_uom(self):
|
||||
@@ -197,16 +199,11 @@ class StockMove(models.Model):
|
||||
move.display_import_lot = (
|
||||
move.has_tracking != 'none' 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
|
||||
|
||||
@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')
|
||||
def _compute_priority(self):
|
||||
for move in self:
|
||||
@@ -255,15 +252,16 @@ class StockMove(models.Model):
|
||||
for move in self:
|
||||
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):
|
||||
self.is_initial_demand_editable = False
|
||||
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
|
||||
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
|
||||
else:
|
||||
move.is_initial_demand_editable = False
|
||||
|
||||
@api.depends('state', 'picking_id', 'product_id')
|
||||
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:
|
||||
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):
|
||||
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)]
|
||||
@@ -588,7 +586,7 @@ Please change the quantity done or the rounding precision of your unit of measur
|
||||
move_line.qty_done = 1
|
||||
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):
|
||||
for move in self:
|
||||
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
|
||||
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')
|
||||
def _check_uom(self):
|
||||
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
|
||||
def create(self, 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')
|
||||
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'):
|
||||
@@ -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
|
||||
# different views to display one field or another so that the webclient doesn't have to
|
||||
# fetch both.
|
||||
if self.show_reserved:
|
||||
view = self.env.ref('stock.view_stock_move_operations')
|
||||
if self.from_immediate_transfer:
|
||||
view = self.env.ref('stock.view_stock_move_operations_immediate')
|
||||
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 {
|
||||
'name': _('Detailed Operations'),
|
||||
'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,
|
||||
'context': dict(
|
||||
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()
|
||||
|
||||
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_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
|
||||
# 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.'))
|
||||
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 []
|
||||
|
||||
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
|
||||
# precision and not the move's UOM to handle case where the `quantity_done` is not
|
||||
# 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'))
|
||||
self.with_context(do_not_unreserve=True).write({'product_uom_qty': new_product_qty})
|
||||
return new_move_vals
|
||||
|
||||
@@ -140,8 +140,7 @@ class StockMoveLine(models.Model):
|
||||
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))
|
||||
else:
|
||||
record.qty_done = record.quant_id.quantity
|
||||
|
||||
record.qty_done = record.quant_id.available_quantity
|
||||
|
||||
@api.constrains('lot_id', 'product_id')
|
||||
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 = sorted(moves, key=lambda m: m.quantity_done < m.product_qty, reverse=True)
|
||||
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:
|
||||
create_move(move_line)
|
||||
else:
|
||||
@@ -352,11 +354,8 @@ class StockMoveLine(models.Model):
|
||||
moves_to_update = mls.filtered(
|
||||
lambda ml:
|
||||
ml.move_id and
|
||||
ml.qty_done and (
|
||||
ml.move_id.state == 'done' or (
|
||||
ml.move_id.picking_id and
|
||||
ml.move_id.picking_id.immediate_transfer
|
||||
))
|
||||
ml.qty_done and
|
||||
ml.move_id.state == 'done'
|
||||
).move_id
|
||||
for move in moves_to_update:
|
||||
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.
|
||||
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 not in ('done', 'cancel') and ml.move_id.picking_id.immediate_transfer).mapped('move_id')
|
||||
for move in moves:
|
||||
move.product_uom_qty = move.quantity_done
|
||||
next_moves._do_unreserve()
|
||||
|
||||
@@ -405,8 +405,8 @@ class Picking(models.Model):
|
||||
compute="_compute_location_id", store=True, precompute=True, readonly=False,
|
||||
check_company=True, required=True)
|
||||
move_ids = fields.One2many('stock.move', 'picking_id', string="Stock Moves", copy=True)
|
||||
move_ids_without_package = fields.One2many(
|
||||
'stock.move', 'picking_id', string="Stock moves not in package", compute='_compute_move_without_package',
|
||||
move_ids_without_package = fields.Many2many(
|
||||
'stock.move', string="Stock moves not in package", compute='_compute_move_without_package',
|
||||
inverse='_set_move_without_package', compute_sudo=True)
|
||||
has_scrap_move = fields.Boolean(
|
||||
'Has Scrap Moves', compute='_has_scrap_move')
|
||||
@@ -864,7 +864,7 @@ class Picking(models.Model):
|
||||
after_vals['partner_id'] = vals['partner_id']
|
||||
if 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()
|
||||
|
||||
return res
|
||||
@@ -1093,7 +1093,7 @@ class Picking(models.Model):
|
||||
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):
|
||||
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)
|
||||
if pickings_using_lots:
|
||||
@@ -1190,6 +1190,18 @@ class Picking(models.Model):
|
||||
pickings_to_backorder = self._check_backorder()
|
||||
if pickings_to_backorder:
|
||||
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
|
||||
|
||||
def _should_show_transfers(self):
|
||||
@@ -1265,6 +1277,8 @@ class Picking(models.Model):
|
||||
immediate_pickings = self.browse()
|
||||
precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
||||
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'))):
|
||||
immediate_pickings |= picking
|
||||
return immediate_pickings
|
||||
|
||||
@@ -545,11 +545,11 @@ class StockQuant(models.Model):
|
||||
"""name that will be displayed in the detailed operation"""
|
||||
for record in self:
|
||||
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)
|
||||
if self.env.user.has_group('stock.group_tracking_lot') and record.package_id:
|
||||
if record.package_id:
|
||||
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)
|
||||
record.display_name = ' - '.join(name)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export class SMLX2ManyField extends X2ManyField {
|
||||
this.selectCreate = (params) => {
|
||||
return selectCreate(params);
|
||||
};
|
||||
this.openRecord = useOpenMany2XRecord({
|
||||
this.openQuantRecord = useOpenMany2XRecord({
|
||||
resModel: "stock.quant",
|
||||
activeActions: this.activeActions,
|
||||
onRecordSaved: (record) => this.selectRecord([record.resId]),
|
||||
@@ -29,6 +29,9 @@ export class SMLX2ManyField extends X2ManyField {
|
||||
}
|
||||
|
||||
async onAdd({ context, editable } = {}) {
|
||||
if (!this.props.record.data.show_quant) {
|
||||
return super.onAdd(...arguments);
|
||||
}
|
||||
context = {
|
||||
...context,
|
||||
single_product: true,
|
||||
@@ -51,12 +54,16 @@ export class SMLX2ManyField extends X2ManyField {
|
||||
const params = {
|
||||
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() {
|
||||
const activeElement = document.activeElement;
|
||||
this.openRecord({
|
||||
this.openQuantRecord({
|
||||
context: {
|
||||
...this.props.context,
|
||||
form_view_ref: "stock.view_stock_quant_form",
|
||||
|
||||
@@ -3,31 +3,22 @@
|
||||
import { registry } from "@web/core/registry";
|
||||
import { ListRenderer } from "@web/views/list/list_renderer";
|
||||
import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field";
|
||||
import { ViewButton } from "@web/views/view_button/view_button";
|
||||
|
||||
class MoveViewButton extends ViewButton {
|
||||
async onClick(ev) {
|
||||
if (this.props.clickParams.name != "action_show_details") {
|
||||
super.onClick(ev);
|
||||
} else {
|
||||
await this.props.record.saveAndOpenDetails();
|
||||
}
|
||||
export class MovesListRenderer extends ListRenderer {}
|
||||
|
||||
MovesListRenderer.props = [ ...ListRenderer.props, 'stockMoveOpen?']
|
||||
|
||||
export class StockMoveX2ManyField extends X2ManyField {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.canOpenRecord = true;
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
if (this.props.clickParams.name == "action_show_details") {
|
||||
return false;
|
||||
}
|
||||
return super.disabled;
|
||||
get isMany2Many() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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 };
|
||||
|
||||
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' && record.data?.show_details_visible">
|
||||
<td class="o_data_cell cursor-pointer o_list_button">
|
||||
<t t-if="record.resModel === 'stock.move' && 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,
|
||||
},
|
||||
]});
|
||||
@@ -24,3 +24,4 @@ from . import test_report_tours
|
||||
from . import test_stock_return_picking
|
||||
from . import test_stock_lot
|
||||
from . import test_picking
|
||||
from . import test_picking_tours
|
||||
|
||||
@@ -37,10 +37,8 @@ class StockMove(TransactionCase):
|
||||
move.product_uom_qty = 1.0
|
||||
picking = picking.save()
|
||||
|
||||
action = picking.move_ids[0].action_show_details()
|
||||
self.assertEqual(action['context']['show_quant'], True)
|
||||
action = picking.move_ids[1].action_show_details()
|
||||
self.assertEqual(action['context']['show_quant'], False)
|
||||
self.assertEqual(picking.move_ids[0].show_quant, True)
|
||||
self.assertEqual(picking.move_ids[1].show_quant, False)
|
||||
|
||||
def test_create_move_line_reserved(self):
|
||||
""" Create a delivery immediate transfer with a storable product.
|
||||
|
||||
@@ -57,7 +57,7 @@ class TestPickShip(TestStockCommon):
|
||||
|
||||
def create_pick_pack_ship(self):
|
||||
picking_ship = self.env['stock.picking'].create({
|
||||
'location_id': self.pack_location,
|
||||
'location_id': self.output_location,
|
||||
'location_dest_id': self.customer_location,
|
||||
'picking_type_id': self.picking_type_out,
|
||||
'state': 'draft',
|
||||
@@ -75,8 +75,8 @@ class TestPickShip(TestStockCommon):
|
||||
})
|
||||
|
||||
picking_pack = self.env['stock.picking'].create({
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.pack_location,
|
||||
'location_id': self.pack_location,
|
||||
'location_dest_id': self.output_location,
|
||||
'picking_type_id': self.picking_type_out,
|
||||
'state': 'draft',
|
||||
'immediate_transfer': False,
|
||||
|
||||
@@ -470,10 +470,10 @@ class TestPacking(TestPackingCommon):
|
||||
# Add 2 lines
|
||||
with receipt_form.move_ids_without_package.new() as move_line:
|
||||
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:
|
||||
move_line.product_id = self.productB
|
||||
move_line.quantity_done = 1
|
||||
move_line.product_uom_qty = 1
|
||||
receipt = receipt_form.save()
|
||||
receipt.action_reset_draft()
|
||||
receipt.action_confirm()
|
||||
@@ -622,10 +622,10 @@ class TestPacking(TestPackingCommon):
|
||||
# Add 2 lines
|
||||
with receipt_form.move_ids_without_package.new() as move_line:
|
||||
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:
|
||||
move_line.product_id = self.productB
|
||||
move_line.quantity_done = 1
|
||||
move_line.product_uom_qty = 1
|
||||
receipt = receipt_form.save()
|
||||
receipt.action_reset_draft()
|
||||
receipt.action_confirm()
|
||||
@@ -906,6 +906,7 @@ class TestPacking(TestPackingCommon):
|
||||
self.assertEqual(len(picking.move_ids), 1, 'Should have only 1 stock move')
|
||||
|
||||
def test_picking_state_with_null_qty(self):
|
||||
""" Exclude empty stock move of the picking state computation """
|
||||
delivery_form = Form(self.env['stock.picking'])
|
||||
picking_type_id = self.warehouse.out_type_id
|
||||
delivery_form.picking_type_id = picking_type_id
|
||||
|
||||
@@ -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)
|
||||
@@ -319,7 +319,7 @@ class TestReports(TestReportsCommon):
|
||||
receipt = receipt_form.save()
|
||||
with receipt_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 2
|
||||
move_line.product_uom_qty = 2
|
||||
receipt = receipt_form.save()
|
||||
receipt.action_reset_draft()
|
||||
|
||||
@@ -338,7 +338,7 @@ class TestReports(TestReportsCommon):
|
||||
delivery = delivery_form.save()
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 5
|
||||
move_line.product_uom_qty = 5
|
||||
delivery = delivery_form.save()
|
||||
delivery.action_reset_draft()
|
||||
|
||||
@@ -679,7 +679,7 @@ class TestReports(TestReportsCommon):
|
||||
delivery = delivery_form.save()
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 5
|
||||
move_line.product_uom_qty = 5
|
||||
delivery = delivery_form.save()
|
||||
delivery.action_reset_draft()
|
||||
|
||||
@@ -722,7 +722,7 @@ class TestReports(TestReportsCommon):
|
||||
delivery_2 = delivery_form.save()
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 8
|
||||
move_line.product_uom_qty = 8
|
||||
delivery_2 = delivery_form.save()
|
||||
delivery_2.action_reset_draft()
|
||||
|
||||
@@ -777,7 +777,7 @@ class TestReports(TestReportsCommon):
|
||||
wh_1_receipt = receipt_form.save()
|
||||
with receipt_form.move_ids_without_package.new() as move_line:
|
||||
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.action_reset_draft()
|
||||
|
||||
@@ -790,7 +790,7 @@ class TestReports(TestReportsCommon):
|
||||
wh_2_receipt = receipt_form.save()
|
||||
with receipt_form.move_ids_without_package.new() as move_line:
|
||||
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.action_reset_draft()
|
||||
|
||||
@@ -951,7 +951,7 @@ class TestReports(TestReportsCommon):
|
||||
delivery_form.picking_type_id = self.picking_type_out
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 100
|
||||
move_line.product_uom_qty = 100
|
||||
delivery = delivery_form.save()
|
||||
delivery.action_confirm()
|
||||
|
||||
@@ -962,7 +962,7 @@ class TestReports(TestReportsCommon):
|
||||
delivery_form.picking_type_id = self.picking_type_out
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 200
|
||||
move_line.product_uom_qty = 200
|
||||
delivery2 = delivery_form.save()
|
||||
delivery2.action_confirm()
|
||||
|
||||
@@ -974,7 +974,7 @@ class TestReports(TestReportsCommon):
|
||||
receipt = receipt_form.save()
|
||||
with receipt_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 200
|
||||
move_line.product_uom_qty = 200
|
||||
receipt = receipt_form.save()
|
||||
receipt.move_ids[0].write({
|
||||
'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
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 100
|
||||
move_line.product_uom_qty = 100
|
||||
delivery = delivery_form.save()
|
||||
delivery.action_confirm()
|
||||
|
||||
@@ -1022,7 +1022,7 @@ class TestReports(TestReportsCommon):
|
||||
delivery_form.picking_type_id = self.picking_type_out
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 200
|
||||
move_line.product_uom_qty = 200
|
||||
delivery2 = delivery_form.save()
|
||||
delivery2.action_confirm()
|
||||
|
||||
@@ -1034,7 +1034,7 @@ class TestReports(TestReportsCommon):
|
||||
receipt = receipt_form.save()
|
||||
with receipt_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 300
|
||||
move_line.product_uom_qty = 300
|
||||
receipt = receipt_form.save()
|
||||
receipt.move_ids[0].write({
|
||||
'move_dest_ids': [(4, delivery2.move_ids[0].id)],
|
||||
@@ -1068,8 +1068,10 @@ class TestReports(TestReportsCommon):
|
||||
delivery_form.scheduled_date = date.today()
|
||||
with delivery_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.product
|
||||
move.quantity_done = 200
|
||||
move.product_uom_qty = 200
|
||||
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
|
||||
scheduled_date1 = datetime.now() + timedelta(days=1)
|
||||
@@ -1079,8 +1081,10 @@ class TestReports(TestReportsCommon):
|
||||
receipt_form.scheduled_date = scheduled_date1
|
||||
with receipt_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.product
|
||||
move.quantity_done = 150
|
||||
move.product_uom_qty = 150
|
||||
receipt1 = receipt_form.save()
|
||||
receipt1.action_reset_draft()
|
||||
receipt1.action_confirm()
|
||||
self.assertEqual(receipt1.move_ids.forecast_availability, -50.0)
|
||||
self.assertEqual(delivery1.move_ids.forecast_availability, 150)
|
||||
self.assertEqual(delivery1.move_ids.forecast_expected_date, scheduled_date1)
|
||||
@@ -1093,14 +1097,17 @@ class TestReports(TestReportsCommon):
|
||||
receipt_form.scheduled_date = scheduled_date2
|
||||
with receipt_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.product
|
||||
move.quantity_done = 150
|
||||
move.product_uom_qty = 150
|
||||
receipt2 = receipt_form.save()
|
||||
receipt2.action_reset_draft()
|
||||
receipt2.action_confirm()
|
||||
|
||||
# Check forecast_information of delivery1
|
||||
delivery1.move_ids._compute_forecast_information() # Because depends not "complete"
|
||||
self.assertEqual(delivery1.move_ids.forecast_availability, 200)
|
||||
self.assertEqual(delivery1.move_ids.forecast_expected_date, scheduled_date2)
|
||||
|
||||
receipt2.move_ids.quantity_done = receipt2.move_ids.product_uom_qty
|
||||
receipt2.button_validate()
|
||||
self.assertEqual(receipt1.move_ids.forecast_availability, 100.0)
|
||||
|
||||
@@ -1113,6 +1120,8 @@ class TestReports(TestReportsCommon):
|
||||
delivery2_form = Form(delivery2)
|
||||
delivery2_form.scheduled_date = datetime.now() + timedelta(days=1)
|
||||
delivery2 = delivery2_form.save()
|
||||
delivery2.action_reset_draft()
|
||||
delivery2.action_confirm()
|
||||
delivery2.move_ids.quantity_done = delivery1.move_ids.quantity_done
|
||||
# To avoid stealing the 150 unit in stock
|
||||
delivery2.do_unreserve()
|
||||
@@ -1159,8 +1168,10 @@ class TestReports(TestReportsCommon):
|
||||
delivery_form.scheduled_date = datetime.now() - timedelta(days=10)
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 3
|
||||
move_line.product_uom_qty = 3
|
||||
delivery_manual = delivery_form.save()
|
||||
delivery_manual.action_reset_draft()
|
||||
delivery_manual.action_confirm()
|
||||
|
||||
# 'by_date' reservation => reservation_date = 1 day before today
|
||||
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)
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
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.action_reset_draft()
|
||||
delivery_by_date.action_confirm()
|
||||
|
||||
# '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)
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 3
|
||||
move_line.product_uom_qty = 3
|
||||
delivery_by_date_priority = delivery_form.save()
|
||||
# <field name="priority" invisible="name == '/'"/>
|
||||
# The priority field is not visible until the name is set,
|
||||
# which is done after a first save / the `create`
|
||||
delivery_form.priority = '1'
|
||||
delivery_by_date_priority = delivery_form.save()
|
||||
delivery_by_date_priority.action_reset_draft()
|
||||
delivery_by_date_priority.action_confirm()
|
||||
|
||||
# 'at_confirm' reservation => reservation_date = today
|
||||
@@ -1201,7 +1214,7 @@ class TestReports(TestReportsCommon):
|
||||
delivery_form.picking_type_id = picking_type_at_confirm
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
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.action_reset_draft()
|
||||
delivery_at_confirm.action_confirm()
|
||||
@@ -1224,8 +1237,9 @@ class TestReports(TestReportsCommon):
|
||||
receipt_form.scheduled_date = date.today() + timedelta(days=1)
|
||||
with receipt_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.product
|
||||
move.quantity_done = 6
|
||||
move.product_uom_qty = 6
|
||||
receipt1 = receipt_form.save()
|
||||
receipt1.action_reset_draft()
|
||||
receipt1.action_confirm()
|
||||
|
||||
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
|
||||
with outgoing.move_ids_without_package.new() as move:
|
||||
move.product_id = self.product
|
||||
move.quantity_done = 2
|
||||
move.product_uom_qty = 2
|
||||
outgoing = outgoing.save()
|
||||
outgoing.action_confirm()
|
||||
incoming = Form(self.env['stock.picking'])
|
||||
incoming.picking_type_id = self.picking_type_in
|
||||
with incoming.move_ids_without_package.new() as move:
|
||||
move.product_id = self.product
|
||||
move.quantity_done = 2
|
||||
move.product_uom_qty = 2
|
||||
incoming = incoming.save()
|
||||
incoming.action_confirm()
|
||||
incoming.action_set_quantities_to_reservation()
|
||||
@@ -1289,10 +1303,10 @@ class TestReports(TestReportsCommon):
|
||||
delivery_form.picking_type_id = self.picking_type_out
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
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:
|
||||
move_line.product_id = product2
|
||||
move_line.quantity_done = 10
|
||||
move_line.product_uom_qty = 10
|
||||
delivery1 = delivery_form.save()
|
||||
delivery1.action_confirm()
|
||||
|
||||
@@ -1303,7 +1317,7 @@ class TestReports(TestReportsCommon):
|
||||
delivery_form.picking_type_id = self.picking_type_out
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 2
|
||||
move_line.product_uom_qty = 2
|
||||
delivery2 = delivery_form.save()
|
||||
delivery2.action_confirm()
|
||||
|
||||
@@ -1316,15 +1330,15 @@ class TestReports(TestReportsCommon):
|
||||
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
|
||||
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:
|
||||
# outgoing qty greater than incoming amount => report line = incoming qty
|
||||
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:
|
||||
# not outgoing => shouldn't appear in report
|
||||
move_line.product_id = product3
|
||||
move_line.quantity_done = 5
|
||||
move_line.product_uom_qty = 5
|
||||
receipt = receipt_form.save()
|
||||
receipt.action_reset_draft()
|
||||
|
||||
@@ -1396,7 +1410,7 @@ class TestReports(TestReportsCommon):
|
||||
delivery_form.picking_type_id = self.picking_type_out
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 100
|
||||
move_line.product_uom_qty = 100
|
||||
delivery = delivery_form.save()
|
||||
delivery.action_reset_draft()
|
||||
delivery.action_confirm()
|
||||
@@ -1409,7 +1423,8 @@ class TestReports(TestReportsCommon):
|
||||
receipt_form.picking_type_id = self.picking_type_in
|
||||
with receipt_form.move_ids_without_package.new() as move_line:
|
||||
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.action_reset_draft()
|
||||
|
||||
@@ -1420,7 +1435,7 @@ class TestReports(TestReportsCommon):
|
||||
receipt_form.picking_type_id = self.picking_type_in
|
||||
with receipt_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 3
|
||||
move_line.product_uom_qty = 3
|
||||
receipt2 = receipt_form.save()
|
||||
receipt2.action_reset_draft()
|
||||
|
||||
@@ -1477,7 +1492,7 @@ class TestReports(TestReportsCommon):
|
||||
delivery_form.picking_type_id = picking_type_out_2
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 100
|
||||
move_line.product_uom_qty = 100
|
||||
delivery = delivery_form.save()
|
||||
delivery.action_confirm()
|
||||
|
||||
@@ -1532,8 +1547,9 @@ class TestReports(TestReportsCommon):
|
||||
receipt_form.picking_type_id = self.picking_type_in
|
||||
with receipt_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = 15
|
||||
move_line.product_uom_qty = 15
|
||||
receipt = receipt_form.save()
|
||||
receipt.action_reset_draft()
|
||||
|
||||
report = self.env['report.stock.report_reception']
|
||||
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
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = outgoing_qty
|
||||
move_line.product_uom_qty = outgoing_qty
|
||||
delivery = delivery_form.save()
|
||||
delivery.action_reset_draft()
|
||||
delivery.action_confirm()
|
||||
|
||||
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
|
||||
with receipt_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = incoming_qty
|
||||
move_line.product_uom_qty = incoming_qty
|
||||
receipt = receipt_form.save()
|
||||
receipt.action_reset_draft()
|
||||
receipt.action_confirm()
|
||||
|
||||
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
|
||||
with delivery_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = outgoing_qty
|
||||
move_line.product_uom_qty = outgoing_qty
|
||||
delivery = delivery_form.save()
|
||||
delivery.action_confirm()
|
||||
|
||||
@@ -1638,7 +1656,7 @@ class TestReports(TestReportsCommon):
|
||||
receipt_form.picking_type_id = self.picking_type_in
|
||||
with receipt_form.move_ids_without_package.new() as move_line:
|
||||
move_line.product_id = self.product
|
||||
move_line.quantity_done = incoming_qty
|
||||
move_line.product_uom_qty = incoming_qty
|
||||
receipt = receipt_form.save()
|
||||
receipt.action_reset_draft()
|
||||
receipt.action_confirm()
|
||||
|
||||
@@ -1927,10 +1927,10 @@ class TestStockFlow(TestStockCommon):
|
||||
f.picking_type_id = warehouse_company_1.out_type_id
|
||||
with f.move_ids_without_package.new() as move:
|
||||
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:
|
||||
move.product_id = product_from_company_3
|
||||
move.quantity_done = 5
|
||||
move.product_uom_qty = 5
|
||||
picking = f.save()
|
||||
picking.action_reset_draft()
|
||||
|
||||
@@ -2024,11 +2024,12 @@ class TestStockFlow(TestStockCommon):
|
||||
|
||||
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
||||
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('reserved_availability', 'column_invisible'))
|
||||
move.product_id = self.productA
|
||||
move.quantity_done = 1
|
||||
move.product_uom_qty = 1
|
||||
picking = picking_form.save()
|
||||
|
||||
self.assertEqual(picking.state, 'assigned')
|
||||
@@ -2067,7 +2068,7 @@ class TestStockFlow(TestStockCommon):
|
||||
picking_form.picking_type_id = picking_type
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = product_lot
|
||||
move.quantity_done = 8
|
||||
move.product_uom_qty = 8
|
||||
receipt_1 = picking_form.save()
|
||||
receipt_1.action_reset_draft()
|
||||
receipt_1.action_confirm()
|
||||
@@ -2088,7 +2089,7 @@ class TestStockFlow(TestStockCommon):
|
||||
picking_form.picking_type_id = picking_type
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = product_lot
|
||||
move.quantity_done = 8
|
||||
move.product_uom_qty = 8
|
||||
receipt_2 = picking_form.save()
|
||||
receipt_2.action_reset_draft()
|
||||
receipt_2.action_confirm()
|
||||
|
||||
@@ -119,9 +119,6 @@
|
||||
<div class="o_kanban_record_headings">
|
||||
<strong class="o_kanban_record_title"><span><field name="product_id" readonly="state == 'done'"/></span></strong>
|
||||
</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 class="o_kanban_record_body">
|
||||
<div invisible="not is_inventory">
|
||||
@@ -158,42 +155,87 @@
|
||||
<field name="display_import_lot" invisible="1"/>
|
||||
<field name="from_immediate_transfer" 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="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>
|
||||
<field name="product_id" readonly="1"/>
|
||||
<label for="product_uom_qty" invisible="from_immediate_transfer"/>
|
||||
<div class="o_row" invisible="from_immediate_transfer">
|
||||
<label for="product_uom_qty"/>
|
||||
<div class="o_row">
|
||||
<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>
|
||||
<label for="quantity_done"/>
|
||||
<div class="o_row">
|
||||
<span><field name="quantity_done" readonly="1" nolabel="1"/></span>
|
||||
<span invisible="state == 'done' or from_immediate_transfer"> / </span>
|
||||
<span><field name="reserved_availability" nolabel="1" invisible="state == 'done' or from_immediate_transfer" /></span>
|
||||
<span><field name="product_uom" nolabel="1" readonly="1" invisible="from_immediate_transfer" options="{'no_open': True}"/></span>
|
||||
<span invisible="state == 'done'"> / </span>
|
||||
<span><field name="reserved_availability" nolabel="1" invisible="state == 'done'" /></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>
|
||||
</group>
|
||||
</group>
|
||||
<field name="move_line_ids"
|
||||
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 > product_uom_qty" nolabel="1"/></span>
|
||||
<span><field name="product_uom" groups="uom.group_uom" readonly="1" decoration-danger="quantity_done > 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"/>
|
||||
<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>
|
||||
</field>
|
||||
</record>
|
||||
@@ -226,19 +268,22 @@
|
||||
<field name="location_id" column_invisible="True"/>
|
||||
<field name="location_dest_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"
|
||||
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}"
|
||||
widget="pick_from"
|
||||
column_invisible="not context.get('show_quant')"
|
||||
column_invisible="not parent.show_quant"
|
||||
options="{'no_create': True, 'no_open': True}"/>
|
||||
<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"
|
||||
domain="[('id', 'child_of', parent.location_dest_id), '|', ('company_id', '=', False), ('company_id', '=', company_id), ('usage', '!=', 'view')]"
|
||||
groups="stock.group_stock_multi_locations"/>
|
||||
<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"
|
||||
domain="[('product_id', '=', parent.product_id), ('company_id', '=', company_id)]"
|
||||
context="{
|
||||
@@ -246,14 +291,14 @@
|
||||
'default_company_id': parent.company_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."
|
||||
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"/>
|
||||
<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="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="reserved_uom_qty" column_invisible="not context.get('show_reserved_quantity')"/>
|
||||
<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="parent.from_immediate_transfer or not parent.show_quant or parent.state == 'done'"/>
|
||||
<field name="state" column_invisible="True"/>
|
||||
<field name="is_locked" column_invisible="True"/>
|
||||
<field name="picking_code" column_invisible="True"/>
|
||||
|
||||
@@ -265,13 +265,14 @@
|
||||
<field name="move_ids_without_package" mode="tree,kanban"
|
||||
widget="stock_move_one2many"
|
||||
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">
|
||||
<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="name" column_invisible="True"/>
|
||||
<field name="state" readonly="0" 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_dest_id" column_invisible="True"/>
|
||||
<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_reserved_availability" 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="move_lines_count" column_invisible="True"/>
|
||||
<field name="is_locked" column_invisible="True"/>
|
||||
<field name="product_uom_category_id" column_invisible="True"/>
|
||||
<field name="has_tracking" 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 > 0"/>
|
||||
<field name="product_id" context="{'default_detailed_type': 'product'}" required="1" readonly="(state != 'draft' and not additional) or move_lines_count > 0" force_save="1"/>
|
||||
<field name="description_picking" string="Description" optional="hide"/>
|
||||
<field name="date" optional="hide"/>
|
||||
<field name="date_deadline" optional="hide"/>
|
||||
<field name="is_initial_demand_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"
|
||||
context="{'default_product_id': 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"
|
||||
invisible="forecast_availability < 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"
|
||||
@@ -307,45 +316,22 @@
|
||||
<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"/>
|
||||
<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"
|
||||
decoration-danger="quantity_done > product_uom_qty"/>
|
||||
<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"/>
|
||||
<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"
|
||||
groups="stock.group_production_lot"
|
||||
invisible="not show_details_visible or has_tracking != 'serial'"
|
||||
optional="hide"
|
||||
options="{'create': [('parent.use_create_lots', '=', True)]}"
|
||||
context="{'default_company_id': company_id, 'default_product_id': product_id, 'active_picking_id': parent.id}"
|
||||
domain="[('product_id','=',product_id)]"/>
|
||||
<button name="action_show_details" type="object" icon="fa-list" title="Details"
|
||||
invisible="not show_details_visible"/>
|
||||
domain="[('product_id','=',product_id)]"
|
||||
/>
|
||||
<button name="action_assign_serial" type="object"
|
||||
icon="fa-plus-square"
|
||||
role="img" title="Assign Serial Numbers"
|
||||
invisible="not display_assign_serial or not show_operations"/>
|
||||
</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 > 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 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'" />
|
||||
|
||||
@@ -375,9 +375,11 @@ class TestBatchPicking(TransactionCase):
|
||||
])
|
||||
self.assertFalse(picking.batch_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(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):
|
||||
with self.assertRaises(UserError):
|
||||
|
||||
Reference in New Issue
Block a user