mirror of
https://github.com/bringout/ventor.git
synced 2026-04-22 05:12:07 +02:00
Initial commit: Ventor Odoo packages (4 packages)
This commit is contained in:
commit
1f20ad87e6
190 changed files with 10375 additions and 0 deletions
|
|
@ -0,0 +1,12 @@
|
|||
# Copyright 2020 VentorTech OU
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
|
||||
|
||||
from . import res_company
|
||||
from . import res_config
|
||||
from . import stock_location
|
||||
from . import stock_picking_mixin
|
||||
from . import stock_picking
|
||||
from . import stock_pack_operation
|
||||
from . import stock_picking_wave
|
||||
from . import stock_quant
|
||||
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
# Copyright 2020 VentorTech OU
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class Company(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
outgoing_routing_strategy = fields.Selection(
|
||||
[
|
||||
# path should be valid for both stock pickings and quants
|
||||
('location_id.removal_prio', 'Location removal priority'),
|
||||
('location_id.name', 'Location name'),
|
||||
('product_id.name', 'Product name'),
|
||||
],
|
||||
string='Picking Strategy', default='location_id.name')
|
||||
|
||||
outgoing_routing_order = fields.Selection(
|
||||
[
|
||||
('0', 'Ascending (A-Z)'),
|
||||
('1', 'Descending (Z-A)'),
|
||||
],
|
||||
string='Picking Order', default='0')
|
||||
|
||||
stock_reservation_strategy = fields.Selection(
|
||||
[
|
||||
('base', 'By Picking Strategy'),
|
||||
('quantity', 'By Quantity'),
|
||||
('none', 'Default'),
|
||||
],
|
||||
string='Reservation Strategy', default='base')
|
||||
|
||||
routing_module_version = fields.Char(
|
||||
string='Routing Module Version',
|
||||
compute='_compute_routing_module_version',
|
||||
compute_sudo=True,
|
||||
)
|
||||
|
||||
def _compute_routing_module_version(self):
|
||||
self.env.cr.execute(
|
||||
"SELECT latest_version FROM ir_module_module WHERE name='outgoing_routing'"
|
||||
)
|
||||
result = self.env.cr.fetchone()
|
||||
full_version = result and result[0]
|
||||
split_value = full_version and full_version.split('.')
|
||||
module_version = split_value and '.'.join(split_value[-3:])
|
||||
|
||||
for rec in self:
|
||||
rec.routing_module_version = module_version
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Copyright 2020 VentorTech OU
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class StockConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
outgoing_routing_strategy = fields.Selection(
|
||||
string='Picking Strategy',
|
||||
related='company_id.outgoing_routing_strategy',
|
||||
readonly=False)
|
||||
|
||||
outgoing_routing_order = fields.Selection(
|
||||
string='Picking Order',
|
||||
related='company_id.outgoing_routing_order',
|
||||
readonly=False)
|
||||
|
||||
stock_reservation_strategy = fields.Selection(
|
||||
string='Reservation Strategy',
|
||||
related='company_id.stock_reservation_strategy',
|
||||
readonly=False)
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
# Copyright 2020 VentorTech OU
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
|
||||
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class StockLocation(models.Model):
|
||||
_inherit = "stock.location"
|
||||
|
||||
removal_prio = fields.Integer(
|
||||
string='Removal Priority',
|
||||
default=0,
|
||||
)
|
||||
|
||||
strategy_sequence = fields.Integer(
|
||||
string='Sequence',
|
||||
help='Sequence based on warehouse location outgoing strategy/order',
|
||||
compute='_compute_outgoing_strategy_sequence',
|
||||
store=False,
|
||||
)
|
||||
|
||||
def _compute_outgoing_strategy_sequence(self):
|
||||
"""
|
||||
"""
|
||||
strategy = self.env.user.company_id.outgoing_routing_strategy
|
||||
strategy_order = self.env.user.company_id.outgoing_routing_order
|
||||
|
||||
if strategy and len(strategy.split('.')) > 1:
|
||||
base, field = strategy.split('.', 1)
|
||||
if base not in ('location_id') and field not in self:
|
||||
return
|
||||
else:
|
||||
return
|
||||
|
||||
res = self.sudo().search([], order='{} {}'.format(
|
||||
field, ['asc', 'desc'][int(strategy_order)]))
|
||||
processed = self.env['stock.location']
|
||||
for sequence, location in enumerate(res):
|
||||
if location not in self:
|
||||
continue
|
||||
location.strategy_sequence = sequence
|
||||
processed |= location
|
||||
remaining_locations = self - processed
|
||||
max_seq = len(res)
|
||||
for remaining in remaining_locations:
|
||||
remaining.strategy_sequence = max_seq
|
||||
|
||||
@api.onchange('location_id')
|
||||
def _onchange_parent_location(self):
|
||||
""" Set location's parent removal priority by default
|
||||
"""
|
||||
if self.location_id:
|
||||
self.removal_prio = self.location_id.removal_prio
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# Copyright 2020 VentorTech OU
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
|
||||
|
||||
from odoo import models, api
|
||||
|
||||
|
||||
class StockPackOperation(models.Model):
|
||||
_inherit = 'stock.move.line'
|
||||
|
||||
@api.model
|
||||
def _compute_operation_valid(self):
|
||||
res = True
|
||||
if hasattr(super(StockPackOperation, self), '_compute_operation_valid'):
|
||||
res &= super(StockPackOperation, self)._compute_operation_valid()
|
||||
res &= self.qty_done != self.reserved_qty
|
||||
return res
|
||||
|
||||
def _get_operation_attr(self, attr, flag):
|
||||
if not flag:
|
||||
return getattr(self, attr)
|
||||
return getattr((self.package_level_id or self), attr)
|
||||
|
||||
def _get_operation_tuple(self):
|
||||
self.ensure_one()
|
||||
show_pack = self.picking_id.picking_type_id.show_entire_packs
|
||||
return (
|
||||
('id', self._get_operation_attr('id', show_pack)),
|
||||
('_type', self._get_operation_attr('_name', show_pack)),
|
||||
)
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
# Copyright 2020 VentorTech OU
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
|
||||
|
||||
from odoo import http
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
import functools
|
||||
|
||||
|
||||
class StockPicking(models.Model):
|
||||
_name = 'stock.picking'
|
||||
_inherit = ['stock.picking', 'stock.picking.mixin']
|
||||
|
||||
operations_to_pick = fields.Many2many(
|
||||
'stock.move.line', relation='picking_operations_to_pick',
|
||||
string='Operations to Pick',
|
||||
compute='_compute_operations_to_pick', store=False)
|
||||
|
||||
strategy_order_r = fields.Char(
|
||||
string='Strategy Order',
|
||||
compute='_compute_operations_to_pick',
|
||||
store=False,
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
'move_line_ids',
|
||||
'move_line_ids.location_id',
|
||||
'move_line_ids.qty_done',
|
||||
)
|
||||
def _compute_operations_to_pick(self):
|
||||
"""
|
||||
"""
|
||||
strategy = self.env.user.company_id.outgoing_routing_strategy
|
||||
strategy_order = self.env.user.company_id.outgoing_routing_order
|
||||
|
||||
for rec in self:
|
||||
all_operations = self.env['stock.move.line'].search([
|
||||
('picking_id', '=', rec.id),
|
||||
])
|
||||
rec.strategy_order_r = rec.get_strategy_string(strategy, strategy_order)
|
||||
rec.operations_to_pick = rec.sort_operations(all_operations, strategy, strategy_order)
|
||||
|
||||
def sort_printer_picking_list(self, move_line_ids):
|
||||
""" sort list of pack operations by configured field
|
||||
"""
|
||||
strategy = self.env.user.company_id.outgoing_routing_strategy
|
||||
strategy_order = self.env.user.company_id.outgoing_routing_order
|
||||
|
||||
return self.sort_operations(move_line_ids, strategy, strategy_order)
|
||||
|
||||
def get_strategy_string(self, strategy, strategy_order):
|
||||
"""
|
||||
"""
|
||||
settings = self.env['res.company'].fields_get([
|
||||
'outgoing_routing_strategy',
|
||||
'outgoing_routing_order',
|
||||
])
|
||||
|
||||
strategies = settings['outgoing_routing_strategy']['selection']
|
||||
orders = settings['outgoing_routing_order']['selection']
|
||||
|
||||
result = _('Hint: operations are sorted by {} in {} order.').format(
|
||||
dict(strategies)[strategy].lower(),
|
||||
dict(orders)[strategy_order].lower()
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def sort_operations(self, all_operations, strategy, strategy_order):
|
||||
"""
|
||||
"""
|
||||
def _r_getattr(obj, attr, *args):
|
||||
return functools.reduce(getattr, [obj] + attr.split('.'))
|
||||
|
||||
validated_operations = all_operations.filtered(lambda op: op._compute_operation_valid())
|
||||
|
||||
result = validated_operations.sorted(
|
||||
key=lambda op: _r_getattr(op, strategy, 'None'),
|
||||
reverse=int(strategy_order)
|
||||
)
|
||||
return result
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
# Copyright 2020 VentorTech OU
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__file__)
|
||||
|
||||
|
||||
class StockPickingMixin(models.AbstractModel):
|
||||
_name = 'stock.picking.mixin'
|
||||
_description = 'Stock Picking Mixin'
|
||||
|
||||
company_id = fields.Many2one(
|
||||
comodel_name='res.company',
|
||||
)
|
||||
routing_module_version = fields.Char(
|
||||
related='company_id.routing_module_version',
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _recheck_record_list(record_list):
|
||||
rechecked_list = []
|
||||
for rec in record_list:
|
||||
if rec.get('_type') == 'stock.package_level' and rec.get('is_done'):
|
||||
continue
|
||||
rechecked_list.append(rec)
|
||||
return rechecked_list
|
||||
|
||||
def _read_record(self, record_tuple):
|
||||
"""
|
||||
record_tuple = (
|
||||
('id', 100),
|
||||
('_type', 'stock.move.line'),
|
||||
)
|
||||
|
||||
id:: number (int)
|
||||
_type:: 'stock.move.line' or 'stock.package_level' (str)
|
||||
"""
|
||||
record_dict = dict(record_tuple)
|
||||
record = self.env[record_dict['_type']].browse(record_dict['id'])
|
||||
record_dict.update(record.read()[0])
|
||||
return record_dict
|
||||
|
||||
def serialize_record_ventor(self, rec_id):
|
||||
"""Record serialization for the Ventor app."""
|
||||
filtered_list = []
|
||||
try:
|
||||
stock_object = self.search([
|
||||
('id', '=', int(rec_id)),
|
||||
])
|
||||
except Exception as ex:
|
||||
_logger.error(ex)
|
||||
return filtered_list
|
||||
|
||||
full_list = [rec._get_operation_tuple() for rec in stock_object.operations_to_pick]
|
||||
[filtered_list.append(rec) for rec in full_list if rec not in filtered_list]
|
||||
record_list = [self._read_record(rec) for rec in filtered_list]
|
||||
return self._recheck_record_list(record_list)
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# Copyright 2020 VentorTech OU
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
|
||||
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class PickingWave(models.Model):
|
||||
_name = 'stock.picking.batch'
|
||||
_inherit = ['stock.picking.batch', 'stock.picking.mixin']
|
||||
|
||||
related_pack_operations = fields.Many2many(
|
||||
'stock.move.line', relation='wave_pack_operations',
|
||||
string='Operations',
|
||||
compute='_compute_related_pack_operations', store=True)
|
||||
|
||||
operations_to_pick = fields.Many2many(
|
||||
'stock.move.line', relation='wave_operations_to_pick',
|
||||
string='Operations to Pick',
|
||||
compute='_compute_operations_to_pick', store=False)
|
||||
|
||||
strategy_order_r = fields.Char(
|
||||
string='Strategy Order',
|
||||
compute='_compute_operations_to_pick',
|
||||
store=False,
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
'picking_ids',
|
||||
'picking_ids.move_line_ids',
|
||||
)
|
||||
def _compute_related_pack_operations(self):
|
||||
for rec in self:
|
||||
res = self.env['stock.move.line']
|
||||
for picking in rec.picking_ids:
|
||||
for operation in picking.move_line_ids:
|
||||
res += operation
|
||||
rec.related_pack_operations = res
|
||||
|
||||
@api.depends(
|
||||
'picking_ids',
|
||||
'picking_ids.move_line_ids',
|
||||
'picking_ids.move_line_ids.location_id',
|
||||
'picking_ids.move_line_ids.qty_done',
|
||||
)
|
||||
def _compute_operations_to_pick(self):
|
||||
strategy = self.env.user.company_id.outgoing_routing_strategy
|
||||
strategy_order = self.env.user.company_id.outgoing_routing_order
|
||||
|
||||
for rec in self:
|
||||
picking_ids = rec.picking_ids.ids
|
||||
all_operations = self.env['stock.move.line'].search([
|
||||
('picking_id', 'in', picking_ids),
|
||||
])
|
||||
rec.strategy_order_r = self.env['stock.picking'].get_strategy_string(strategy,
|
||||
strategy_order)
|
||||
rec.operations_to_pick = self.env['stock.picking'].sort_operations(all_operations,
|
||||
strategy,
|
||||
strategy_order)
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
# Copyright 2020 VentorTech OU
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
|
||||
|
||||
from odoo import api, models, fields
|
||||
from odoo.tools.float_utils import float_compare
|
||||
|
||||
import functools
|
||||
|
||||
|
||||
class StockQuant(models.Model):
|
||||
_inherit = 'stock.quant'
|
||||
|
||||
removal_prio = fields.Integer(
|
||||
related="location_id.removal_prio",
|
||||
store=True,
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _get_removal_strategy_order(self, removal_strategy):
|
||||
# THIS IS A OVERRIDE STANDARD METHOD
|
||||
strategy_order = self.env.user.company_id.outgoing_routing_order
|
||||
|
||||
if removal_strategy == 'location_priority':
|
||||
return 'removal_prio %s, id' % (['ASC', 'DESC'][int(strategy_order)])
|
||||
return super(StockQuant, self)._get_removal_strategy_order(removal_strategy)
|
||||
|
||||
@api.model
|
||||
def _update_reserved_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, strict=False):
|
||||
""" Updates reserved quantity in quants
|
||||
"""
|
||||
self = self.with_context(reservation_strategy=self.env.user.company_id.stock_reservation_strategy, reservation_quantity=quantity)
|
||||
return super(StockQuant, self)._update_reserved_quantity(product_id, location_id, quantity, lot_id, package_id, owner_id, strict)
|
||||
|
||||
def _gather(self, product_id, location_id, lot_id=None, package_id=None, owner_id=None, strict=False):
|
||||
""" Gather (and reorder, if required) quants
|
||||
"""
|
||||
context = dict(self.env.context)
|
||||
quants = super(StockQuant, self)._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict)
|
||||
|
||||
if self._context.get('skip_ventor_reordering') or (product_id.categ_id and product_id.categ_id.removal_strategy_id)\
|
||||
or location_id.removal_strategy_id:
|
||||
return quants
|
||||
|
||||
strategy = self.env.user.company_id.stock_reservation_strategy
|
||||
|
||||
func_reorder = getattr(self, '_reorder_{}'.format(strategy))
|
||||
if func_reorder:
|
||||
quants = func_reorder(quants, product_id)
|
||||
|
||||
# mom taught us to clean after ourselves
|
||||
if context.get('reservation_strategy'):
|
||||
del context['reservation_strategy']
|
||||
if context.get('reservation_quantity'):
|
||||
del context['reservation_quantity']
|
||||
self = self.with_context(context)
|
||||
|
||||
return quants
|
||||
|
||||
def _reorder_none(self, quants, product_id):
|
||||
""" No reorder, i.e. use out of the box strategy
|
||||
"""
|
||||
return quants
|
||||
|
||||
def _reorder_base(self, quants, product_id):
|
||||
""" Reorders quants by location removal priority
|
||||
"""
|
||||
def _r_getattr(obj, attr, *args):
|
||||
return functools.reduce(getattr, [obj] + attr.split('.'))
|
||||
|
||||
route = self.env.user.company_id.outgoing_routing_strategy
|
||||
order = self.env.user.company_id.outgoing_routing_order
|
||||
|
||||
return quants.sorted(
|
||||
key=lambda op: _r_getattr(op, route, 'None'),
|
||||
reverse=int(order)
|
||||
)
|
||||
|
||||
def _reorder_quantity(self, quants, product_id):
|
||||
""" Reorders quants by product quantity in locations and location priority
|
||||
"""
|
||||
default_route = 'name' # i.e. location_id.name
|
||||
|
||||
route = self.env.user.company_id.outgoing_routing_strategy
|
||||
order = self.env.user.company_id.outgoing_routing_order
|
||||
|
||||
base, field = route.split('.', 1)
|
||||
startegy = field if base in ('location_id') else default_route
|
||||
|
||||
required = self.env.context.get('reservation_quantity', 0)
|
||||
rounding = product_id.uom_id.rounding
|
||||
|
||||
locations = {}
|
||||
queues = [self.env['stock.quant'], self.env['stock.quant']] # (lprio, hprio)
|
||||
|
||||
for quant in quants:
|
||||
locations.setdefault(quant.location_id, []).append(quant)
|
||||
|
||||
for location in sorted(locations,
|
||||
key=lambda location: getattr(location, startegy, 'None'),
|
||||
reverse=int(order)
|
||||
):
|
||||
location_quants = locations.get(location)
|
||||
quantity = sum([qt.quantity - qt.reserved_quantity for qt in location_quants])
|
||||
priority = float_compare(quantity, required, precision_rounding=rounding) >= 0
|
||||
for location_quant in location_quants:
|
||||
queues[priority] |= location_quant
|
||||
|
||||
return queues[True] + queues[False]
|
||||
Loading…
Add table
Add a link
Reference in a new issue