[REF] stock, delivery: adapt packages to new implementation
This commit is contained in:
committed by
Simon Lejeune
parent
ff91120416
commit
809605bb8f
@@ -11,14 +11,11 @@ class StockQuantPackage(models.Model):
|
||||
_inherit = "stock.quant.package"
|
||||
|
||||
@api.one
|
||||
@api.depends('quant_ids', 'children_ids')
|
||||
@api.depends('quant_ids')
|
||||
def _compute_weight(self):
|
||||
weight = 0
|
||||
for quant in self.quant_ids:
|
||||
weight += quant.qty * quant.product_id.weight
|
||||
for pack in self.children_ids:
|
||||
pack._compute_weight()
|
||||
weight += pack.weight
|
||||
self.weight = weight
|
||||
|
||||
weight = fields.Float(compute='_compute_weight')
|
||||
|
||||
@@ -251,6 +251,7 @@
|
||||
<field name="inherit_id" ref="stock.view_quant_package_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="company_id" position="before">
|
||||
<field name="packaging_id"/>
|
||||
<field name="weight"/>
|
||||
<field name="shipping_weight"/>
|
||||
</field>
|
||||
|
||||
@@ -40,9 +40,8 @@ class ChooseDeliveryPackage(models.TransientModel):
|
||||
return stock_quant_package.shipping_weight
|
||||
else:
|
||||
picking_id = self.env['stock.picking'].browse(self.env.context['active_id'])
|
||||
pack_operation_product_ids = [po for po in picking_id.pack_operation_product_ids if po.qty_done > 0 and not po.result_package_id]
|
||||
pack_operation_pack_ids = [po for po in picking_id.pack_operation_pack_ids if po.qty_done > 0 and not po.result_package_id]
|
||||
total_weight = sum([po.qty_done * po.product_id.weight for po in pack_operation_product_ids]) + sum([po.package_id.weight for po in pack_operation_pack_ids])
|
||||
pack_operation_ids = [po for po in picking_id.pack_operation_ids if po.qty_done > 0 and not po.result_package_id]
|
||||
total_weight = sum([po.qty_done * po.product_id.weight for po in pack_operation_ids])
|
||||
return total_weight
|
||||
|
||||
def put_in_pack(self):
|
||||
|
||||
@@ -178,10 +178,10 @@ class StockMove(models.Model):
|
||||
multi_locations_enabled = False
|
||||
if self.user_has_groups('stock.group_stock_multi_locations'):
|
||||
multi_locations_enabled = move.location_id.child_ids or move.location_dest_id.child_ids
|
||||
|
||||
has_package = move.pack_operation_ids.mapped('package_id') | move.pack_operation_ids.mapped('result_package_id')
|
||||
if move.picking_id.picking_type_id.show_operations is False\
|
||||
and move.state not in ['cancel', 'draft', 'confirmed']\
|
||||
and (multi_locations_enabled or move.has_tracking != 'none' or len(move.pack_operation_ids) > 1):
|
||||
and (multi_locations_enabled or move.has_tracking != 'none' or len(move.pack_operation_ids) > 1 or has_package):
|
||||
move.show_details_visible = True
|
||||
else:
|
||||
move.show_details_visible = False
|
||||
@@ -790,6 +790,7 @@ class StockMove(models.Model):
|
||||
break
|
||||
if move.state != 'partially_available':
|
||||
move.state = 'partially_available'
|
||||
self.mapped('picking_id')._check_entire_pack()
|
||||
|
||||
@api.multi
|
||||
def action_cancel(self):
|
||||
|
||||
@@ -29,12 +29,12 @@ class PackOperation(models.Model):
|
||||
product_uom_qty = fields.Float('Reserved', default=0.0, digits=dp.get_precision('Product Unit of Measure'), required=True)
|
||||
ordered_qty = fields.Float('Ordered Quantity', digits=dp.get_precision('Product Unit of Measure'))
|
||||
qty_done = fields.Float('Done', default=0.0, digits=dp.get_precision('Product Unit of Measure'), copy=False)
|
||||
package_id = fields.Many2one('stock.quant.package', 'Source Package')
|
||||
package_id = fields.Many2one('stock.quant.package', 'Source Package', ondelete='restrict')
|
||||
lot_id = fields.Many2one('stock.production.lot', 'Lot')
|
||||
lot_name = fields.Char('Lot/Serial Number')
|
||||
result_package_id = fields.Many2one(
|
||||
'stock.quant.package', 'Destination Package',
|
||||
ondelete='cascade', required=False,
|
||||
ondelete='restrict', required=False,
|
||||
help="If set, the operations are packed into this package")
|
||||
date = fields.Datetime('Date', default=fields.Datetime.now(), required=True)
|
||||
owner_id = fields.Many2one('res.partner', 'Owner', help="Owner of the quants")
|
||||
|
||||
@@ -5,11 +5,13 @@ from collections import namedtuple
|
||||
import json
|
||||
import time
|
||||
|
||||
from itertools import groupby
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, pycompat
|
||||
from odoo.tools.float_utils import float_compare
|
||||
from odoo.tools.float_utils import float_compare, float_round
|
||||
from odoo.addons.procurement.models import procurement
|
||||
from odoo.exceptions import UserError
|
||||
from operator import itemgetter
|
||||
|
||||
|
||||
class PickingType(models.Model):
|
||||
@@ -274,6 +276,10 @@ class Picking(models.Model):
|
||||
'Has Pack Operations', compute='_compute_pack_operation_exist',
|
||||
help='Check the existence of pack operation on the picking')
|
||||
|
||||
has_packages = fields.Boolean(
|
||||
'Has Packages', compute='_compute_has_packages',
|
||||
help='Check the existence of destination packages on move lines')
|
||||
|
||||
owner_id = fields.Many2one(
|
||||
'res.partner', 'Owner',
|
||||
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
|
||||
@@ -367,6 +373,15 @@ class Picking(models.Model):
|
||||
def _compute_pack_operation_exist(self):
|
||||
self.pack_operation_exist = bool(self.pack_operation_ids)
|
||||
|
||||
@api.one
|
||||
def _compute_has_packages(self):
|
||||
has_packages = False
|
||||
for pack_op in self.pack_operation_ids:
|
||||
if pack_op.result_package_id:
|
||||
has_packages = True
|
||||
break
|
||||
self.has_packages = has_packages
|
||||
|
||||
@api.onchange('picking_type_id', 'partner_id')
|
||||
def onchange_picking_type(self):
|
||||
if self.picking_type_id:
|
||||
@@ -474,6 +489,7 @@ class Picking(models.Model):
|
||||
if not moves:
|
||||
raise UserError(_('Nothing to check the availability for.'))
|
||||
moves.action_assign()
|
||||
self._check_entire_pack()
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
@@ -731,26 +747,26 @@ class Picking(models.Model):
|
||||
|
||||
@api.multi
|
||||
def _put_in_pack(self):
|
||||
# TDE FIXME: reclean me
|
||||
QuantPackage = self.env["stock.quant.package"]
|
||||
package = False
|
||||
for pick in self:
|
||||
operations = [x for x in pick.pack_operation_ids if x.qty_done > 0 and (not x.result_package_id)]
|
||||
pack_operation_ids = self.env['stock.pack.operation']
|
||||
for operation in operations:
|
||||
# If we haven't done all qty in operation, we have to split into 2 operation
|
||||
op = operation
|
||||
if operation.qty_done < operation.product_qty:
|
||||
new_operation = operation.copy({'product_qty': operation.qty_done,'qty_done': operation.qty_done})
|
||||
|
||||
operation.write({'product_qty': operation.product_qty - operation.qty_done,'qty_done': 0})
|
||||
|
||||
op = new_operation
|
||||
pack_operation_ids |= op
|
||||
operations = pick.pack_operation_ids.filtered(lambda o: o.qty_done > 0 and not o.result_package_id)
|
||||
operation_ids = self.env['stock.pack.operation']
|
||||
if operations:
|
||||
pack_operation_ids.check_tracking()
|
||||
package = QuantPackage.create({})
|
||||
pack_operation_ids.write({'result_package_id': package.id})
|
||||
package = self.env['stock.quant.package'].create({})
|
||||
for operation in operations:
|
||||
if float_compare(operation.qty_done, operation.product_qty, precision_rounding=operation.product_uom_id.rounding) >= 0:
|
||||
operation_ids |= operation
|
||||
else:
|
||||
quantity_left_todo = float_round(
|
||||
operation.product_qty - operation.qty_done,
|
||||
precision_rounding=operation.product_uom_id.rounding,
|
||||
rounding_method='UP')
|
||||
new_operation = operation.copy(
|
||||
default={'product_uom_qty': operation.qty_done, 'qty_done': operation.qty_done})
|
||||
operation.write({'product_uom_qty': quantity_left_todo, 'qty_done': 0.0})
|
||||
operation_ids |= new_operation
|
||||
|
||||
operation_ids.write({'result_package_id': package.id})
|
||||
else:
|
||||
raise UserError(_('Please process some quantities to put in the pack first!'))
|
||||
return package
|
||||
@@ -782,3 +798,12 @@ class Picking(models.Model):
|
||||
scraps = self.env['stock.scrap'].search([('picking_id', '=', self.id)])
|
||||
action['domain'] = [('id', 'in', scraps.ids)]
|
||||
return action
|
||||
|
||||
@api.multi
|
||||
def action_see_packages(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref('stock.action_package_view').read()[0]
|
||||
packages = self.pack_operation_ids.mapped('result_package_id')
|
||||
action['domain'] = [('id', 'in', packages.ids)]
|
||||
action['context'] = {'picking_id': self.id}
|
||||
return action
|
||||
|
||||
@@ -32,7 +32,7 @@ class StockQuant(models.Model):
|
||||
ondelete='restrict', readonly=True)
|
||||
package_id = fields.Many2one(
|
||||
'stock.quant.package', 'Package',
|
||||
help='The package containing this quant', readonly=True)
|
||||
help='The package containing this quant', readonly=True, ondelete='restrict')
|
||||
owner_id = fields.Many2one(
|
||||
'res.partner', 'Owner',
|
||||
help='This is the owner of the quant', readonly=True)
|
||||
@@ -233,13 +233,6 @@ class QuantPackage(models.Model):
|
||||
'Package Reference', copy=False, index=True,
|
||||
default=lambda self: self.env['ir.sequence'].next_by_code('stock.quant.package') or _('Unknown Pack'))
|
||||
quant_ids = fields.One2many('stock.quant', 'package_id', 'Bulk Content', readonly=True)
|
||||
parent_id = fields.Many2one(
|
||||
'stock.quant.package', 'Parent Package',
|
||||
ondelete='restrict', readonly=True,
|
||||
help="The package containing this item")
|
||||
ancestor_ids = fields.One2many('stock.quant.package', string='Ancestors', compute='_compute_ancestor_ids')
|
||||
children_quant_ids = fields.One2many('stock.quant', string='All Bulk Content', compute='_compute_children_quant_ids')
|
||||
children_ids = fields.One2many('stock.quant.package', 'parent_id', 'Contained Packages', readonly=True)
|
||||
packaging_id = fields.Many2one(
|
||||
'product.packaging', 'Package Type', index=True,
|
||||
help="This field should be completed only if everything inside the package share the same product, otherwise it doesn't really makes sense.")
|
||||
@@ -252,28 +245,14 @@ class QuantPackage(models.Model):
|
||||
owner_id = fields.Many2one(
|
||||
'res.partner', 'Owner', compute='_compute_package_info', search='_search_owner',
|
||||
index=True, readonly=True)
|
||||
move_line_ids = fields.One2many('stock.pack.operation', 'result_package_id')
|
||||
current_picking_move_line_ids = fields.One2many('stock.pack.operation', compute="_compute_current_picking_info")
|
||||
current_picking_id = fields.Boolean(compute="_compute_current_picking_info")
|
||||
|
||||
@api.one
|
||||
@api.depends('parent_id', 'children_ids')
|
||||
def _compute_ancestor_ids(self):
|
||||
if self.id:
|
||||
self.ancestor_ids = self.env['stock.quant.package'].search(['id', 'parent_of', self.id]).ids
|
||||
|
||||
@api.multi
|
||||
@api.depends('parent_id', 'children_ids', 'quant_ids.package_id')
|
||||
def _compute_children_quant_ids(self):
|
||||
for package in self:
|
||||
if package.id:
|
||||
package.children_quant_ids = self.env['stock.quant'].search([('package_id', 'child_of', package.id)]).ids
|
||||
|
||||
@api.depends('quant_ids.package_id', 'quant_ids.location_id', 'quant_ids.company_id', 'quant_ids.owner_id', 'ancestor_ids')
|
||||
@api.depends('quant_ids.package_id', 'quant_ids.location_id', 'quant_ids.company_id', 'quant_ids.owner_id')
|
||||
def _compute_package_info(self):
|
||||
for package in self:
|
||||
quants = package.children_quant_ids
|
||||
if quants:
|
||||
values = quants[0]
|
||||
else:
|
||||
values = {'location_id': False, 'company_id': self.env.user.company_id.id, 'owner_id': False}
|
||||
values = {'location_id': False, 'company_id': self.env.user.company_id.id, 'owner_id': False}
|
||||
package.location_id = values['location_id']
|
||||
package.company_id = values['company_id']
|
||||
package.owner_id = values['owner_id']
|
||||
@@ -286,14 +265,20 @@ class QuantPackage(models.Model):
|
||||
""" Forms complete name of location from parent location to child location. """
|
||||
res = {}
|
||||
for package in self:
|
||||
current = package
|
||||
name = current.name
|
||||
while current.parent_id:
|
||||
name = '%s / %s' % (current.parent_id.name, name)
|
||||
current = current.parent_id
|
||||
name = package.name
|
||||
res[package.id] = name
|
||||
return res
|
||||
|
||||
def _compute_current_picking_info(self):
|
||||
picking_id = self.env.context.get('picking_id')
|
||||
if picking_id:
|
||||
self.current_picking_move_line_ids = self.move_line_ids.filtered(lambda move_line: move_line.picking_id.id == picking_id)
|
||||
self.current_picking_id = True
|
||||
else:
|
||||
self.current_picking_move_line_ids = False
|
||||
self.current_picking_id = False
|
||||
|
||||
|
||||
def _search_location(self, operator, value):
|
||||
if value:
|
||||
packs = self.search([('quant_ids.location_id', operator, value)])
|
||||
@@ -330,10 +315,7 @@ class QuantPackage(models.Model):
|
||||
package)
|
||||
'''
|
||||
for pack in self:
|
||||
parent = pack
|
||||
while parent.parent_id:
|
||||
parent = parent.parent_id
|
||||
locations = parent.get_content().filtered(lambda quant: quant.qty > 0.0).mapped('location_id')
|
||||
locations = pack.get_content().filtered(lambda quant: quant.qty > 0.0).mapped('location_id')
|
||||
if len(locations) != 1:
|
||||
raise UserError(_('Everything inside a package should be in the same location'))
|
||||
return True
|
||||
@@ -341,10 +323,11 @@ class QuantPackage(models.Model):
|
||||
@api.multi
|
||||
def unpack(self):
|
||||
for package in self:
|
||||
# TDE FIXME: why superuser ?
|
||||
package.mapped('quant_ids').sudo().write({'package_id': package.parent_id.id})
|
||||
package.mapped('children_ids').write({'parent_id': package.parent_id.id})
|
||||
return self.env['ir.actions.act_window'].for_xml_id('stock', 'action_package_view')
|
||||
move_lines_to_remove = self.move_line_ids.filtered(lambda move_line: move_line.state != 'done')
|
||||
if move_lines_to_remove:
|
||||
move_lines_to_remove.write({'result_package_id': False})
|
||||
else:
|
||||
package.mapped('quant_ids').write({'package_id': False})
|
||||
|
||||
def action_view_picking(self):
|
||||
action = self.env.ref('stock.action_picking_tree_all').read()[0]
|
||||
|
||||
@@ -215,6 +215,7 @@
|
||||
<button name="do_unreserve" string="Unreserve" groups="base.group_user" type="object" attrs="{'invisible': ['|', ('picking_type_code', '=', 'incoming'), ('state', 'not in', ('assigned', 'partially_available'))]}"/>
|
||||
<field name="show_operations" invisible="1"/>
|
||||
<field name="pack_operation_exist" invisible="1"/>
|
||||
<field name="has_packages" invisible="1"/>
|
||||
<button name="button_scrap" type="object" string="Scrap" attrs="{'invisible': ['|', '&', ('picking_type_code', '!=', 'incoming'), ('state', 'in', ('draft', 'cancel', 'waiting')), '&', ('picking_type_code', '=', 'incoming'), ('state', '!=', 'done')]}"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed,partially_available,assigned,done" />
|
||||
<field name="picking_type_entire_packs" invisible="1"/>
|
||||
@@ -225,6 +226,10 @@
|
||||
<button name="action_see_move_scrap" string="Scraps" type="object"
|
||||
class="oe_stat_button" icon="fa-arrows-v"
|
||||
attrs="{'invisible': [('has_scrap_move', '=', False)]}"/>
|
||||
<button name="action_see_packages" string="Packages" type="object"
|
||||
class="oe_stat_button" icon="fa-arrows-v"
|
||||
attrs="{'invisible': [('has_packages', '=', False)]}"/>
|
||||
<button name="%(action_stock_report)d" icon="fa-arrow-up" class="oe_stat_button" string="Up/Down Traceability" type="action" states="done" groups="stock.group_production_lot"/>
|
||||
</div>
|
||||
<h1 class="hidden-xs">
|
||||
<field name="name" attrs="{'invisible': [('name','=','/')]}" readonly="1"/>
|
||||
@@ -258,7 +263,7 @@
|
||||
<field name="package_id" groups="stock.group_tracking_lot" invisible="1"/>
|
||||
<field name="location_id" domain="[('id', 'child_of', parent.location_id)]" invisible="1"/>
|
||||
<field name="location_dest_id" domain="[('id', 'child_of', parent.location_dest_id)]" invisible="1"/>
|
||||
<field name="result_package_id" readonly="1" groups="stock.group_tracking_lot" context="{'default_location_id': location_dest_id}" invisible="1"/>
|
||||
<field name="result_package_id" groups="stock.group_tracking_lot" context="{'default_location_id': location_dest_id}" invisible="1"/>
|
||||
<field name="lots_visible" invisible="1"/>
|
||||
<field name="owner_id" groups="stock.group_tracking_owner"/>
|
||||
<field name="state" invisible="1"/>
|
||||
|
||||
@@ -140,14 +140,13 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Package">
|
||||
<header>
|
||||
<button name="unpack" string="Unpack" type="object" groups="base.group_no_one"/>
|
||||
<button name="unpack" string="Unpack" type="object"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" name="action_view_picking"
|
||||
string="Package Transfers" type="object"
|
||||
widget="statinfo" icon="fa-dropbox"/>
|
||||
<button name="get_content_package" type="object" string="View Contained Packages content" icon="fa-cogs" attrs="{'invisible': [('children_ids','=',[])]}"/>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<label for="name" string="Package Reference" class="oe_edit_only"/>
|
||||
@@ -155,34 +154,22 @@
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="packaging_id"/>
|
||||
<field name='company_id' groups="base.group_multi_company"/>
|
||||
<field name='owner_id' groups="stock.group_tracking_owner"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="location_id"/>
|
||||
<field name="parent_id"/>
|
||||
<field name='company_id' groups="base.group_multi_company"/>
|
||||
<field name='owner_id' groups="stock.group_tracking_owner"/>
|
||||
<field name="location_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Content">
|
||||
<separator string="Bulk Content" />
|
||||
<field name="quant_ids">
|
||||
<tree string="Quants">
|
||||
<separator string="Products" />
|
||||
<field name="current_picking_id" invisible="1"/>
|
||||
<field name="current_picking_move_line_ids" readonly="1" attrs="{'invisible': [('current_picking_id', '=', False)]}">
|
||||
<tree>
|
||||
<field name="product_id"/>
|
||||
<field name="quantity"/>
|
||||
<field name="location_id"/>
|
||||
<field name="in_date"/>
|
||||
</tree>
|
||||
</field>
|
||||
<separator string="Contained Packages"/>
|
||||
<field name="children_ids">
|
||||
<tree string="Contained Packages">
|
||||
<field name="display_name"/>
|
||||
<field name="packaging_id"/>
|
||||
<field name="location_id"/>
|
||||
<field name="qty_done" string="Quantity"/>
|
||||
</tree>
|
||||
</field>
|
||||
<field name="quant_ids" attrs="{'invisible': [('current_picking_id', '=', True)]}"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
@@ -204,10 +191,30 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_quant_package_kanban">
|
||||
<field name="name">stock.quant.package.kanban</field>
|
||||
<field name="model">stock.quant.package</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban>
|
||||
<templates>
|
||||
<field name="name"/>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div class="oe_kanban_vignette">
|
||||
<strong><field name="name"/></strong>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_package_view">
|
||||
<field name="context">{}</field>
|
||||
<field name="name">Packages</field>
|
||||
<field name="res_model">stock.quant.package</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p>Packages are usually created by pack operations made on transfers and can contains several different products. You can then reuse a package to move its whole content somewhere else, or to pack it into another bigger package. A package can also be unpacked, allowing the disposal of its former content as single units again.
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user