[IMP] stock_request: black, isort

This commit is contained in:
hveficent
2020-01-28 16:53:34 +01:00
committed by Kitti U
parent 13dc2169c7
commit 854bbe132d
19 changed files with 1375 additions and 1095 deletions

View File

@@ -1,2 +1 @@
from . import models from . import models

View File

@@ -7,12 +7,9 @@
"version": "12.0.1.1.6", "version": "12.0.1.1.6",
"license": "LGPL-3", "license": "LGPL-3",
"website": "https://github.com/stock-logistics-warehouse", "website": "https://github.com/stock-logistics-warehouse",
"author": "Eficent, " "author": "Eficent, " "Odoo Community Association (OCA)",
"Odoo Community Association (OCA)",
"category": "Warehouse Management", "category": "Warehouse Management",
"depends": [ "depends": ["stock"],
"stock",
],
"data": [ "data": [
"security/stock_request_security.xml", "security/stock_request_security.xml",
"security/ir.model.access.csv", "security/ir.model.access.csv",

View File

@@ -5,15 +5,16 @@ from odoo import api, models
class ProcurementGroup(models.Model): class ProcurementGroup(models.Model):
_inherit = 'procurement.group' _inherit = "procurement.group"
@api.model @api.model
def run(self, product_id, product_qty, product_uom, location_id, name, def run(
origin, values): self, product_id, product_qty, product_uom, location_id, name, origin, values
if 'stock_request_id' in values and values.get('stock_request_id'): ):
req = self.env['stock.request'].browse( if "stock_request_id" in values and values.get("stock_request_id"):
values.get('stock_request_id')) req = self.env["stock.request"].browse(values.get("stock_request_id"))
if req.order_id: if req.order_id:
origin = req.order_id.name origin = req.order_id.name
return super().run(product_id, product_qty, product_uom, location_id, return super().run(
name, origin, values) product_id, product_qty, product_uom, location_id, name, origin, values
)

View File

@@ -7,8 +7,8 @@ from odoo import fields, models
class ResCompany(models.Model): class ResCompany(models.Model):
_inherit = 'res.company' _inherit = "res.company"
stock_request_allow_virtual_loc = fields.Boolean( stock_request_allow_virtual_loc = fields.Boolean(
string='Allow Virtual locations on Stock Requests', string="Allow Virtual locations on Stock Requests"
) )

View File

@@ -5,29 +5,34 @@ from odoo import api, fields, models
class ResConfigSettings(models.TransientModel): class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings' _inherit = "res.config.settings"
group_stock_request_order = fields.Boolean( group_stock_request_order = fields.Boolean(
implied_group='stock_request.group_stock_request_order') implied_group="stock_request.group_stock_request_order"
)
module_stock_request_purchase = fields.Boolean( module_stock_request_purchase = fields.Boolean(
string='Stock Requests for Purchases') string="Stock Requests for Purchases"
)
module_stock_request_kanban = fields.Boolean( module_stock_request_kanban = fields.Boolean(
string='Stock Requests Kanban integration') string="Stock Requests Kanban integration"
)
stock_request_allow_virtual_loc = fields.Boolean( stock_request_allow_virtual_loc = fields.Boolean(
related='company_id.stock_request_allow_virtual_loc', related="company_id.stock_request_allow_virtual_loc", readonly=False
readonly=False) )
module_stock_request_analytic = fields.Boolean( module_stock_request_analytic = fields.Boolean(
string='Stock Requests Analytic integration') string="Stock Requests Analytic integration"
)
module_stock_request_submit = fields.Boolean( module_stock_request_submit = fields.Boolean(
string='Submitted state in Stock Requests') string="Submitted state in Stock Requests"
)
# Dependencies # Dependencies
@api.onchange('stock_request_allow_virtual_loc') @api.onchange("stock_request_allow_virtual_loc")
def _onchange_stock_request_allow_virtual_loc(self): def _onchange_stock_request_allow_virtual_loc(self):
if self.stock_request_allow_virtual_loc: if self.stock_request_allow_virtual_loc:
self.group_stock_multi_locations = True self.group_stock_multi_locations = True

View File

@@ -1,26 +1,45 @@
# Copyright 2018 Eficent Business and IT Consulting Services, S.L. # Copyright 2018 Eficent Business and IT Consulting Services, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, models, _ from odoo import _, api, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
class StockLocation(models.Model): class StockLocation(models.Model):
_inherit = 'stock.location' _inherit = "stock.location"
@api.constrains('company_id') @api.constrains("company_id")
def _check_company_stock_request(self): def _check_company_stock_request(self):
if any(rec.company_id and self.env['stock.request'].search( if any(
[('company_id', '!=', rec.company_id.id), rec.company_id
('location_id', '=', rec.id)], limit=1) for rec in self): and self.env["stock.request"].search(
[("company_id", "!=", rec.company_id.id), ("location_id", "=", rec.id)],
limit=1,
)
for rec in self
):
raise ValidationError( raise ValidationError(
_('You cannot change the company of the location, as it is ' _(
'already assigned to stock requests that belong to ' "You cannot change the company of the location, as it is "
'another company.')) "already assigned to stock requests that belong to "
if any(rec.company_id and self.env['stock.request.order'].search( "another company."
[('company_id', '!=', rec.company_id.id), )
('warehouse_id', '=', rec.id)], limit=1) for rec in self): )
if any(
rec.company_id
and self.env["stock.request.order"].search(
[
("company_id", "!=", rec.company_id.id),
("warehouse_id", "=", rec.id),
],
limit=1,
)
for rec in self
):
raise ValidationError( raise ValidationError(
_('You cannot change the company of the location, as it is ' _(
'already assigned to stock request orders that belong to ' "You cannot change the company of the location, as it is "
'another company.')) "already assigned to stock request orders that belong to "
"another company."
)
)

View File

@@ -1,19 +1,27 @@
# Copyright 2018 Eficent Business and IT Consulting Services, S.L. # Copyright 2018 Eficent Business and IT Consulting Services, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, models, _ from odoo import _, api, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
class StockLocationRoute(models.Model): class StockLocationRoute(models.Model):
_inherit = 'stock.location.route' _inherit = "stock.location.route"
@api.constrains('company_id') @api.constrains("company_id")
def _check_company_stock_request(self): def _check_company_stock_request(self):
if any(rec.company_id and self.env['stock.request'].search( if any(
[('company_id', '!=', rec.company_id.id), rec.company_id
('route_id', '=', rec.id)], limit=1) for rec in self): and self.env["stock.request"].search(
[("company_id", "!=", rec.company_id.id), ("route_id", "=", rec.id)],
limit=1,
)
for rec in self
):
raise ValidationError( raise ValidationError(
_('You cannot change the company of the route, as it is ' _(
'already assigned to stock requests that belong to ' "You cannot change the company of the route, as it is "
'another company.')) "already assigned to stock requests that belong to "
"another company."
)
)

View File

@@ -1,39 +1,50 @@
# Copyright 2017 Eficent Business and IT Consulting Services, S.L. # Copyright 2017 Eficent Business and IT Consulting Services, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, fields, models, _ from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
class StockMove(models.Model): class StockMove(models.Model):
_inherit = 'stock.move' _inherit = "stock.move"
allocation_ids = fields.One2many(comodel_name='stock.request.allocation', allocation_ids = fields.One2many(
inverse_name='stock_move_id', comodel_name="stock.request.allocation",
string='Stock Request Allocation') inverse_name="stock_move_id",
string="Stock Request Allocation",
)
stock_request_ids = fields.One2many(comodel_name='stock.request', stock_request_ids = fields.One2many(
string='Stock Requests', comodel_name="stock.request",
compute='_compute_stock_request_ids') string="Stock Requests",
compute="_compute_stock_request_ids",
)
@api.depends('allocation_ids') @api.depends("allocation_ids")
def _compute_stock_request_ids(self): def _compute_stock_request_ids(self):
for rec in self: for rec in self:
rec.stock_request_ids = rec.allocation_ids.mapped( rec.stock_request_ids = rec.allocation_ids.mapped("stock_request_id")
'stock_request_id')
def _merge_moves_fields(self): def _merge_moves_fields(self):
res = super(StockMove, self)._merge_moves_fields() res = super(StockMove, self)._merge_moves_fields()
res['allocation_ids'] = [(4, m.id) for m in res["allocation_ids"] = [(4, m.id) for m in self.mapped("allocation_ids")]
self.mapped('allocation_ids')]
return res return res
@api.constrains('company_id') @api.constrains("company_id")
def _check_company_stock_request(self): def _check_company_stock_request(self):
if any(self.env['stock.request.allocation'].search( if any(
[('company_id', '!=', rec.company_id.id), self.env["stock.request.allocation"].search(
('stock_move_id', '=', rec.id)], limit=1) [
for rec in self): ("company_id", "!=", rec.company_id.id),
("stock_move_id", "=", rec.id),
],
limit=1,
)
for rec in self
):
raise ValidationError( raise ValidationError(
_('The company of the stock request must match with ' _(
'that of the location.')) "The company of the stock request must match with "
"that of the location."
)
)

View File

@@ -9,39 +9,44 @@ class StockMoveLine(models.Model):
@api.model @api.model
def _stock_request_confirm_done_message_content(self, message_data): def _stock_request_confirm_done_message_content(self, message_data):
title = _('Receipt confirmation %s for your Request %s') % ( title = _("Receipt confirmation %s for your Request %s") % (
message_data['picking_name'], message_data['request_name']) message_data["picking_name"],
message = '<h3>%s</h3>' % title message_data["request_name"],
message += _('The following requested items from Stock Request %s ' )
'have now been received in %s using Picking %s:') % ( message = "<h3>%s</h3>" % title
message_data['request_name'], message_data['location_name'],
message_data['picking_name'])
message += '<ul>'
message += _( message += _(
'<li><b>%s</b>: Transferred quantity %s %s</li>' "The following requested items from Stock Request %s "
) % (message_data['product_name'], "have now been received in %s using Picking %s:"
message_data['product_qty'], ) % (
message_data['product_uom'], message_data["request_name"],
) message_data["location_name"],
message += '</ul>' message_data["picking_name"],
)
message += "<ul>"
message += _("<li><b>%s</b>: Transferred quantity %s %s</li>") % (
message_data["product_name"],
message_data["product_qty"],
message_data["product_uom"],
)
message += "</ul>"
return message return message
def _prepare_message_data(self, ml, request, allocated_qty): def _prepare_message_data(self, ml, request, allocated_qty):
return { return {
'request_name': request.name, "request_name": request.name,
'picking_name': ml.picking_id.name, "picking_name": ml.picking_id.name,
'product_name': ml.product_id.name_get()[0][1], "product_name": ml.product_id.name_get()[0][1],
'product_qty': allocated_qty, "product_qty": allocated_qty,
'product_uom': ml.product_uom_id.name, "product_uom": ml.product_uom_id.name,
'location_name': ml.location_dest_id.name_get()[0][1], "location_name": ml.location_dest_id.name_get()[0][1],
} }
def _action_done(self): def _action_done(self):
res = super(StockMoveLine, self)._action_done() res = super(StockMoveLine, self)._action_done()
for ml in self.filtered( for ml in self.filtered(lambda m: m.exists() and m.move_id.allocation_ids):
lambda m: m.exists() and m.move_id.allocation_ids):
qty_done = ml.product_uom_id._compute_quantity( qty_done = ml.product_uom_id._compute_quantity(
ml.qty_done, ml.product_id.uom_id) ml.qty_done, ml.product_id.uom_id
)
# We do sudo because potentially the user that completes the move # We do sudo because potentially the user that completes the move
# may not have permissions for stock.request. # may not have permissions for stock.request.
@@ -49,16 +54,12 @@ class StockMoveLine(models.Model):
for allocation in ml.move_id.allocation_ids.sudo(): for allocation in ml.move_id.allocation_ids.sudo():
allocated_qty = 0.0 allocated_qty = 0.0
if allocation.open_product_qty: if allocation.open_product_qty:
allocated_qty = min( allocated_qty = min(allocation.open_product_qty, qty_done)
allocation.open_product_qty, qty_done)
allocation.allocated_product_qty += allocated_qty allocation.allocated_product_qty += allocated_qty
to_allocate_qty -= allocated_qty to_allocate_qty -= allocated_qty
request = allocation.stock_request_id request = allocation.stock_request_id
message_data = self._prepare_message_data(ml, request, message_data = self._prepare_message_data(ml, request, allocated_qty)
allocated_qty) message = self._stock_request_confirm_done_message_content(message_data)
message = \ request.message_post(body=message, subtype="mail.mt_comment")
self._stock_request_confirm_done_message_content(
message_data)
request.message_post(body=message, subtype='mail.mt_comment')
request.check_done() request.check_done()
return res return res

View File

@@ -5,33 +5,35 @@ from odoo import api, fields, models
class StockPicking(models.Model): class StockPicking(models.Model):
_inherit = 'stock.picking' _inherit = "stock.picking"
stock_request_ids = fields.One2many(comodel_name='stock.request', stock_request_ids = fields.One2many(
string='Stock Requests', comodel_name="stock.request",
compute='_compute_stock_request_ids') string="Stock Requests",
stock_request_count = fields.Integer('Stock Request #', compute="_compute_stock_request_ids",
compute='_compute_stock_request_ids') )
stock_request_count = fields.Integer(
"Stock Request #", compute="_compute_stock_request_ids"
)
@api.depends('move_lines') @api.depends("move_lines")
def _compute_stock_request_ids(self): def _compute_stock_request_ids(self):
for rec in self: for rec in self:
rec.stock_request_ids = rec.move_lines.mapped('stock_request_ids') rec.stock_request_ids = rec.move_lines.mapped("stock_request_ids")
rec.stock_request_count = len(rec.stock_request_ids) rec.stock_request_count = len(rec.stock_request_ids)
def action_view_stock_request(self): def action_view_stock_request(self):
""" """
:return dict: dictionary value for created view :return dict: dictionary value for created view
""" """
action = self.env.ref( action = self.env.ref("stock_request.action_stock_request_form").read()[0]
'stock_request.action_stock_request_form').read()[0]
requests = self.mapped('stock_request_ids') requests = self.mapped("stock_request_ids")
if len(requests) > 1: if len(requests) > 1:
action['domain'] = [('id', 'in', requests.ids)] action["domain"] = [("id", "in", requests.ids)]
elif requests: elif requests:
action['views'] = [ action["views"] = [
(self.env.ref('stock_request.view_stock_request_form').id, (self.env.ref("stock_request.view_stock_request_form").id, "form")
'form')] ]
action['res_id'] = requests.id action["res_id"] = requests.id
return action return action

View File

@@ -1,23 +1,25 @@
# Copyright 2017 Eficent Business and IT Consulting Services, S.L. # Copyright 2017 Eficent Business and IT Consulting Services, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, fields, models, _ from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
from odoo.addons import decimal_precision as dp
from odoo.tools import float_compare from odoo.tools import float_compare
from odoo.addons import decimal_precision as dp
REQUEST_STATES = [ REQUEST_STATES = [
('draft', 'Draft'), ("draft", "Draft"),
('open', 'In progress'), ("open", "In progress"),
('done', 'Done'), ("done", "Done"),
('cancel', 'Cancelled')] ("cancel", "Cancelled"),
]
class StockRequest(models.Model): class StockRequest(models.Model):
_name = "stock.request" _name = "stock.request"
_description = "Stock Request" _description = "Stock Request"
_inherit = 'stock.request.abstract' _inherit = "stock.request.abstract"
_order = 'id desc' _order = "id desc"
def __get_request_states(self): def __get_request_states(self):
return REQUEST_STATES return REQUEST_STATES
@@ -26,7 +28,7 @@ class StockRequest(models.Model):
return self.__get_request_states() return self.__get_request_states()
def _get_default_requested_by(self): def _get_default_requested_by(self):
return self.env['res.users'].browse(self.env.uid) return self.env["res.users"].browse(self.env.uid)
@staticmethod @staticmethod
def _get_expected_date(): def _get_expected_date():
@@ -39,178 +41,177 @@ class StockRequest(models.Model):
res = self._get_expected_date() res = self._get_expected_date()
return res return res
name = fields.Char( name = fields.Char(states={"draft": [("readonly", False)]})
states={'draft': [('readonly', False)]}
)
state = fields.Selection( state = fields.Selection(
selection=_get_request_states, string='Status', selection=_get_request_states,
copy=False, default='draft', index=True, string="Status",
readonly=True, track_visibility='onchange', copy=False,
default="draft",
index=True,
readonly=True,
track_visibility="onchange",
) )
requested_by = fields.Many2one( requested_by = fields.Many2one(
'res.users', 'Requested by', required=True, "res.users",
track_visibility='onchange', "Requested by",
required=True,
track_visibility="onchange",
default=lambda s: s._get_default_requested_by(), default=lambda s: s._get_default_requested_by(),
) )
expected_date = fields.Datetime( expected_date = fields.Datetime(
'Expected Date', default=lambda s: s._get_default_expected_date(), "Expected Date",
index=True, required=True, readonly=True, default=lambda s: s._get_default_expected_date(),
states={'draft': [('readonly', False)]}, index=True,
required=True,
readonly=True,
states={"draft": [("readonly", False)]},
help="Date when you expect to receive the goods.", help="Date when you expect to receive the goods.",
) )
picking_policy = fields.Selection([ picking_policy = fields.Selection(
('direct', 'Receive each product when available'), [
('one', 'Receive all products at once')], ("direct", "Receive each product when available"),
string='Shipping Policy', required=True, readonly=True, ("one", "Receive all products at once"),
states={'draft': [('readonly', False)]}, ],
default='direct', string="Shipping Policy",
required=True,
readonly=True,
states={"draft": [("readonly", False)]},
default="direct",
)
move_ids = fields.One2many(
comodel_name="stock.move",
compute="_compute_move_ids",
string="Stock Moves",
readonly=True,
)
picking_ids = fields.One2many(
"stock.picking",
compute="_compute_picking_ids",
string="Pickings",
readonly=True,
) )
move_ids = fields.One2many(comodel_name='stock.move',
compute='_compute_move_ids',
string='Stock Moves', readonly=True,
)
picking_ids = fields.One2many('stock.picking',
compute='_compute_picking_ids',
string='Pickings', readonly=True,
)
qty_in_progress = fields.Float( qty_in_progress = fields.Float(
'Qty In Progress', digits=dp.get_precision('Product Unit of Measure'), "Qty In Progress",
readonly=True, compute='_compute_qty', store=True, digits=dp.get_precision("Product Unit of Measure"),
readonly=True,
compute="_compute_qty",
store=True,
help="Quantity in progress.", help="Quantity in progress.",
) )
qty_done = fields.Float( qty_done = fields.Float(
'Qty Done', digits=dp.get_precision('Product Unit of Measure'), "Qty Done",
readonly=True, compute='_compute_qty', store=True, digits=dp.get_precision("Product Unit of Measure"),
readonly=True,
compute="_compute_qty",
store=True,
help="Quantity completed", help="Quantity completed",
) )
picking_count = fields.Integer(string='Delivery Orders', picking_count = fields.Integer(
compute='_compute_picking_ids', string="Delivery Orders", compute="_compute_picking_ids", readonly=True
readonly=True,
)
allocation_ids = fields.One2many(comodel_name='stock.request.allocation',
inverse_name='stock_request_id',
string='Stock Request Allocation')
order_id = fields.Many2one(
'stock.request.order',
readonly=True,
) )
allocation_ids = fields.One2many(
comodel_name="stock.request.allocation",
inverse_name="stock_request_id",
string="Stock Request Allocation",
)
order_id = fields.Many2one("stock.request.order", readonly=True)
warehouse_id = fields.Many2one( warehouse_id = fields.Many2one(
states={'draft': [('readonly', False)]}, readonly=True states={"draft": [("readonly", False)]}, readonly=True
) )
location_id = fields.Many2one( location_id = fields.Many2one(
states={'draft': [('readonly', False)]}, readonly=True states={"draft": [("readonly", False)]}, readonly=True
)
product_id = fields.Many2one(
states={'draft': [('readonly', False)]}, readonly=True
) )
product_id = fields.Many2one(states={"draft": [("readonly", False)]}, readonly=True)
product_uom_id = fields.Many2one( product_uom_id = fields.Many2one(
states={'draft': [('readonly', False)]}, readonly=True states={"draft": [("readonly", False)]}, readonly=True
) )
product_uom_qty = fields.Float( product_uom_qty = fields.Float(
states={'draft': [('readonly', False)]}, readonly=True states={"draft": [("readonly", False)]}, readonly=True
) )
procurement_group_id = fields.Many2one( procurement_group_id = fields.Many2one(
states={'draft': [('readonly', False)]}, readonly=True states={"draft": [("readonly", False)]}, readonly=True
)
company_id = fields.Many2one(
states={'draft': [('readonly', False)]}, readonly=True
)
route_id = fields.Many2one(
states={'draft': [('readonly', False)]}, readonly=True
) )
company_id = fields.Many2one(states={"draft": [("readonly", False)]}, readonly=True)
route_id = fields.Many2one(states={"draft": [("readonly", False)]}, readonly=True)
_sql_constraints = [ _sql_constraints = [
('name_uniq', 'unique(name, company_id)', ("name_uniq", "unique(name, company_id)", "Stock Request name must be unique")
'Stock Request name must be unique'),
] ]
@api.depends('allocation_ids') @api.depends("allocation_ids")
def _compute_move_ids(self): def _compute_move_ids(self):
for request in self: for request in self:
request.move_ids = request.allocation_ids.mapped('stock_move_id') request.move_ids = request.allocation_ids.mapped("stock_move_id")
@api.depends('allocation_ids') @api.depends("allocation_ids")
def _compute_picking_ids(self): def _compute_picking_ids(self):
for request in self: for request in self:
request.picking_count = 0 request.picking_count = 0
request.picking_ids = self.env['stock.picking'] request.picking_ids = self.env["stock.picking"]
request.picking_ids = request.move_ids.filtered( request.picking_ids = request.move_ids.filtered(
lambda m: m.state != 'cancel').mapped('picking_id') lambda m: m.state != "cancel"
).mapped("picking_id")
request.picking_count = len(request.picking_ids) request.picking_count = len(request.picking_ids)
@api.depends('allocation_ids', 'allocation_ids.stock_move_id.state', @api.depends(
'allocation_ids.stock_move_id.move_line_ids', "allocation_ids",
'allocation_ids.stock_move_id.move_line_ids.qty_done') "allocation_ids.stock_move_id.state",
"allocation_ids.stock_move_id.move_line_ids",
"allocation_ids.stock_move_id.move_line_ids.qty_done",
)
def _compute_qty(self): def _compute_qty(self):
for request in self: for request in self:
done_qty = sum(request.allocation_ids.mapped( done_qty = sum(request.allocation_ids.mapped("allocated_product_qty"))
'allocated_product_qty')) open_qty = sum(request.allocation_ids.mapped("open_product_qty"))
open_qty = sum(request.allocation_ids.mapped('open_product_qty'))
request.qty_done = request.product_id.uom_id._compute_quantity( request.qty_done = request.product_id.uom_id._compute_quantity(
done_qty, request.product_uom_id) done_qty, request.product_uom_id
request.qty_in_progress = \ )
request.product_id.uom_id._compute_quantity( request.qty_in_progress = request.product_id.uom_id._compute_quantity(
open_qty, request.product_uom_id) open_qty, request.product_uom_id
)
@api.constrains('order_id', 'requested_by') @api.constrains("order_id", "requested_by")
def check_order_requested_by(self): def check_order_requested_by(self):
if self.order_id and self.order_id.requested_by != self.requested_by: if self.order_id and self.order_id.requested_by != self.requested_by:
raise ValidationError(_( raise ValidationError(_("Requested by must be equal to the order"))
'Requested by must be equal to the order'
))
@api.constrains('order_id', 'warehouse_id') @api.constrains("order_id", "warehouse_id")
def check_order_warehouse_id(self): def check_order_warehouse_id(self):
if self.order_id and self.order_id.warehouse_id != self.warehouse_id: if self.order_id and self.order_id.warehouse_id != self.warehouse_id:
raise ValidationError(_( raise ValidationError(_("Warehouse must be equal to the order"))
'Warehouse must be equal to the order'
))
@api.constrains('order_id', 'location_id') @api.constrains("order_id", "location_id")
def check_order_location(self): def check_order_location(self):
if self.order_id and self.order_id.location_id != self.location_id: if self.order_id and self.order_id.location_id != self.location_id:
raise ValidationError(_( raise ValidationError(_("Location must be equal to the order"))
'Location must be equal to the order'
))
@api.constrains('order_id', 'procurement_group_id') @api.constrains("order_id", "procurement_group_id")
def check_order_procurement_group(self): def check_order_procurement_group(self):
if ( if (
self.order_id and self.order_id
self.order_id.procurement_group_id != self.procurement_group_id and self.order_id.procurement_group_id != self.procurement_group_id
): ):
raise ValidationError(_( raise ValidationError(_("Procurement group must be equal to the order"))
'Procurement group must be equal to the order'
))
@api.constrains('order_id', 'company_id') @api.constrains("order_id", "company_id")
def check_order_company(self): def check_order_company(self):
if self.order_id and self.order_id.company_id != self.company_id: if self.order_id and self.order_id.company_id != self.company_id:
raise ValidationError(_( raise ValidationError(_("Company must be equal to the order"))
'Company must be equal to the order'
))
@api.constrains('order_id', 'expected_date') @api.constrains("order_id", "expected_date")
def check_order_expected_date(self): def check_order_expected_date(self):
if self.order_id and self.order_id.expected_date != self.expected_date: if self.order_id and self.order_id.expected_date != self.expected_date:
raise ValidationError(_( raise ValidationError(_("Expected date must be equal to the order"))
'Expected date must be equal to the order'
))
@api.constrains('order_id', 'picking_policy') @api.constrains("order_id", "picking_policy")
def check_order_picking_policy(self): def check_order_picking_policy(self):
if ( if self.order_id and self.order_id.picking_policy != self.picking_policy:
self.order_id and raise ValidationError(_("The picking policy must be equal to the order"))
self.order_id.picking_policy != self.picking_policy
):
raise ValidationError(_(
'The picking policy must be equal to the order'
))
@api.multi @api.multi
def _action_confirm(self): def _action_confirm(self):
self._action_launch_procurement_rule() self._action_launch_procurement_rule()
self.state = 'open' self.state = "open"
@api.multi @api.multi
def action_confirm(self): def action_confirm(self):
@@ -218,30 +219,35 @@ class StockRequest(models.Model):
return True return True
def action_draft(self): def action_draft(self):
self.write({'state': 'draft'}) self.write({"state": "draft"})
return True return True
def action_cancel(self): def action_cancel(self):
self.sudo().mapped('move_ids')._action_cancel() self.sudo().mapped("move_ids")._action_cancel()
self.state = 'cancel' self.state = "cancel"
return True return True
def action_done(self): def action_done(self):
self.state = 'done' self.state = "done"
if self.order_id: if self.order_id:
self.order_id.check_done() self.order_id.check_done()
return True return True
def check_done(self): def check_done(self):
precision = self.env['decimal.precision'].precision_get( precision = self.env["decimal.precision"].precision_get(
'Product Unit of Measure') "Product Unit of Measure"
)
for request in self: for request in self:
allocated_qty = sum(request.allocation_ids.mapped( allocated_qty = sum(request.allocation_ids.mapped("allocated_product_qty"))
'allocated_product_qty'))
qty_done = request.product_id.uom_id._compute_quantity( qty_done = request.product_id.uom_id._compute_quantity(
allocated_qty, request.product_uom_id) allocated_qty, request.product_uom_id
if float_compare(qty_done, request.product_uom_qty, )
precision_digits=precision) >= 0: if (
float_compare(
qty_done, request.product_uom_qty, precision_digits=precision
)
>= 0
):
request.action_done() request.action_done()
return True return True
@@ -254,17 +260,16 @@ class StockRequest(models.Model):
move/po creation. move/po creation.
""" """
return { return {
'date_planned': self.expected_date, "date_planned": self.expected_date,
'warehouse_id': self.warehouse_id, "warehouse_id": self.warehouse_id,
'stock_request_allocation_ids': self.id, "stock_request_allocation_ids": self.id,
'group_id': group_id or self.procurement_group_id.id or False, "group_id": group_id or self.procurement_group_id.id or False,
'route_ids': self.route_id, "route_ids": self.route_id,
'stock_request_id': self.id, "stock_request_id": self.id,
} }
def _skip_procurement(self): def _skip_procurement(self):
return self.state != 'draft' or \ return self.state != "draft" or self.product_id.type not in ("consu", "product")
self.product_id.type not in ('consu', 'product')
@api.multi @api.multi
def _action_launch_procurement_rule(self): def _action_launch_procurement_rule(self):
@@ -275,61 +280,63 @@ class StockRequest(models.Model):
'_run_buy' or '_run_manufacture' '_run_buy' or '_run_manufacture'
depending on the stock request product rule. depending on the stock request product rule.
""" """
precision = self.env['decimal.precision'].precision_get( precision = self.env["decimal.precision"].precision_get(
'Product Unit of Measure') "Product Unit of Measure"
)
errors = [] errors = []
for request in self: for request in self:
if request._skip_procurement(): if request._skip_procurement():
continue continue
qty = 0.0 qty = 0.0
for move in request.move_ids.filtered( for move in request.move_ids.filtered(lambda r: r.state != "cancel"):
lambda r: r.state != 'cancel'):
qty += move.product_qty qty += move.product_qty
if float_compare(qty, request.product_qty, if float_compare(qty, request.product_qty, precision_digits=precision) >= 0:
precision_digits=precision) >= 0:
continue continue
values = request._prepare_procurement_values( values = request._prepare_procurement_values(
group_id=request.procurement_group_id) group_id=request.procurement_group_id
)
try: try:
# We launch with sudo because potentially we could create # We launch with sudo because potentially we could create
# objects that the user is not authorized to create, such # objects that the user is not authorized to create, such
# as PO. # as PO.
self.env['procurement.group'].sudo().run( self.env["procurement.group"].sudo().run(
request.product_id, request.product_uom_qty, request.product_id,
request.product_uom_qty,
request.product_uom_id, request.product_uom_id,
request.location_id, request.name, request.location_id,
request.name, values) request.name,
request.name,
values,
)
except UserError as error: except UserError as error:
errors.append(error.name) errors.append(error.name)
if errors: if errors:
raise UserError('\n'.join(errors)) raise UserError("\n".join(errors))
return True return True
@api.multi @api.multi
def action_view_transfer(self): def action_view_transfer(self):
action = self.env.ref('stock.action_picking_tree_all').read()[0] action = self.env.ref("stock.action_picking_tree_all").read()[0]
pickings = self.mapped('picking_ids') pickings = self.mapped("picking_ids")
if len(pickings) > 1: if len(pickings) > 1:
action['domain'] = [('id', 'in', pickings.ids)] action["domain"] = [("id", "in", pickings.ids)]
elif pickings: elif pickings:
action['views'] = [ action["views"] = [(self.env.ref("stock.view_picking_form").id, "form")]
(self.env.ref('stock.view_picking_form').id, 'form')] action["res_id"] = pickings.id
action['res_id'] = pickings.id
return action return action
@api.model @api.model
def create(self, vals): def create(self, vals):
upd_vals = vals.copy() upd_vals = vals.copy()
if upd_vals.get('name', '/') == '/': if upd_vals.get("name", "/") == "/":
upd_vals['name'] = self.env['ir.sequence'].next_by_code( upd_vals["name"] = self.env["ir.sequence"].next_by_code("stock.request")
'stock.request')
return super().create(upd_vals) return super().create(upd_vals)
@api.multi @api.multi
def unlink(self): def unlink(self):
if self.filtered(lambda r: r.state != 'draft'): if self.filtered(lambda r: r.state != "draft"):
raise UserError(_('Only requests on draft state can be unlinked')) raise UserError(_("Only requests on draft state can be unlinked"))
return super(StockRequest, self).unlink() return super(StockRequest, self).unlink()

View File

@@ -1,118 +1,133 @@
# Copyright 2017 Eficent Business and IT Consulting Services, S.L. # Copyright 2017 Eficent Business and IT Consulting Services, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, fields, models, _ from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from odoo.addons import decimal_precision as dp from odoo.addons import decimal_precision as dp
class StockRequest(models.AbstractModel): class StockRequest(models.AbstractModel):
_name = "stock.request.abstract" _name = "stock.request.abstract"
_description = "Stock Request Template" _description = "Stock Request Template"
_inherit = ['mail.thread', 'mail.activity.mixin'] _inherit = ["mail.thread", "mail.activity.mixin"]
@api.model @api.model
def default_get(self, fields): def default_get(self, fields):
res = super(StockRequest, self).default_get(fields) res = super(StockRequest, self).default_get(fields)
warehouse = None warehouse = None
if 'warehouse_id' not in res and res.get('company_id'): if "warehouse_id" not in res and res.get("company_id"):
warehouse = self.env['stock.warehouse'].search( warehouse = self.env["stock.warehouse"].search(
[('company_id', '=', res['company_id'])], limit=1) [("company_id", "=", res["company_id"])], limit=1
)
if warehouse: if warehouse:
res['warehouse_id'] = warehouse.id res["warehouse_id"] = warehouse.id
res['location_id'] = warehouse.lot_stock_id.id res["location_id"] = warehouse.lot_stock_id.id
return res return res
@api.depends('product_id', 'product_uom_id', 'product_uom_qty', @api.depends(
'product_id.product_tmpl_id.uom_id') "product_id",
"product_uom_id",
"product_uom_qty",
"product_id.product_tmpl_id.uom_id",
)
def _compute_product_qty(self): def _compute_product_qty(self):
for rec in self: for rec in self:
rec.product_qty = rec.product_uom_id._compute_quantity( rec.product_qty = rec.product_uom_id._compute_quantity(
rec.product_uom_qty, rec.product_id.product_tmpl_id.uom_id) rec.product_uom_qty, rec.product_id.product_tmpl_id.uom_id
)
name = fields.Char( name = fields.Char("Name", copy=False, required=True, readonly=True, default="/")
'Name', copy=False, required=True, readonly=True,
default='/')
warehouse_id = fields.Many2one( warehouse_id = fields.Many2one(
'stock.warehouse', 'Warehouse', "stock.warehouse", "Warehouse", ondelete="cascade", required=True
ondelete="cascade", required=True,
) )
location_id = fields.Many2one( location_id = fields.Many2one(
'stock.location', 'Location', "stock.location",
domain=[('usage', 'in', ['internal', 'transit'])], "Location",
ondelete="cascade", required=True, domain=[("usage", "in", ["internal", "transit"])],
ondelete="cascade",
required=True,
) )
product_id = fields.Many2one( product_id = fields.Many2one(
'product.product', 'Product', "product.product",
domain=[('type', 'in', ['product', 'consu'])], ondelete='cascade', "Product",
domain=[("type", "in", ["product", "consu"])],
ondelete="cascade",
required=True, required=True,
) )
allow_virtual_location = fields.Boolean( allow_virtual_location = fields.Boolean(
related='company_id.stock_request_allow_virtual_loc', related="company_id.stock_request_allow_virtual_loc", readonly=True
readonly=True,
) )
product_uom_id = fields.Many2one( product_uom_id = fields.Many2one(
'uom.uom', 'Product Unit of Measure', "uom.uom",
"Product Unit of Measure",
required=True, required=True,
default=lambda self: self._context.get('product_uom_id', False), default=lambda self: self._context.get("product_uom_id", False),
) )
product_uom_qty = fields.Float( product_uom_qty = fields.Float(
'Quantity', digits=dp.get_precision('Product Unit of Measure'), "Quantity",
digits=dp.get_precision("Product Unit of Measure"),
required=True, required=True,
help="Quantity, specified in the unit of measure indicated in the " help="Quantity, specified in the unit of measure indicated in the " "request.",
"request.",
) )
product_qty = fields.Float( product_qty = fields.Float(
'Real Quantity', compute='_compute_product_qty', "Real Quantity",
store=True, copy=False, compute="_compute_product_qty",
digits=dp.get_precision('Product Unit of Measure'), store=True,
help='Quantity in the default UoM of the product', copy=False,
digits=dp.get_precision("Product Unit of Measure"),
help="Quantity in the default UoM of the product",
) )
procurement_group_id = fields.Many2one( procurement_group_id = fields.Many2one(
'procurement.group', 'Procurement Group', "procurement.group",
"Procurement Group",
help="Moves created through this stock request will be put in this " help="Moves created through this stock request will be put in this "
"procurement group. If none is given, the moves generated by " "procurement group. If none is given, the moves generated by "
"procurement rules will be grouped into one big picking.", "procurement rules will be grouped into one big picking.",
) )
company_id = fields.Many2one( company_id = fields.Many2one(
'res.company', 'Company', required=True, "res.company",
default=lambda self: self.env['res.company']._company_default_get( "Company",
'stock.request'), required=True,
default=lambda self: self.env["res.company"]._company_default_get(
"stock.request"
),
)
route_id = fields.Many2one(
"stock.location.route",
string="Route",
domain="[('id', 'in', route_ids)]",
ondelete="restrict",
) )
route_id = fields.Many2one('stock.location.route', string='Route',
domain="[('id', 'in', route_ids)]",
ondelete='restrict')
route_ids = fields.Many2many( route_ids = fields.Many2many(
'stock.location.route', string='Routes', "stock.location.route",
compute='_compute_route_ids', string="Routes",
compute="_compute_route_ids",
readonly=True, readonly=True,
) )
_sql_constraints = [ _sql_constraints = [
('name_uniq', 'unique(name, company_id)', ("name_uniq", "unique(name, company_id)", "Name must be unique")
'Name must be unique'),
] ]
@api.depends('product_id', 'warehouse_id', 'location_id') @api.depends("product_id", "warehouse_id", "location_id")
def _compute_route_ids(self): def _compute_route_ids(self):
route_obj = self.env['stock.location.route'] route_obj = self.env["stock.location.route"]
for wh in self.mapped('warehouse_id'): for wh in self.mapped("warehouse_id"):
wh_routes = route_obj.search( wh_routes = route_obj.search([("warehouse_ids", "=", wh.id)])
[('warehouse_ids', '=', wh.id)])
for record in self.filtered(lambda r: r.warehouse_id == wh): for record in self.filtered(lambda r: r.warehouse_id == wh):
routes = route_obj routes = route_obj
if record.product_id: if record.product_id:
routes += record.product_id.mapped( routes += record.product_id.mapped(
'route_ids' "route_ids"
) | record.product_id.mapped( ) | record.product_id.mapped("categ_id").mapped("total_route_ids")
'categ_id'
).mapped('total_route_ids')
if record.warehouse_id: if record.warehouse_id:
routes |= wh_routes routes |= wh_routes
parents = record.get_parents().ids parents = record.get_parents().ids
record.route_ids = routes.filtered(lambda r: any( record.route_ids = routes.filtered(
p.location_id.id in parents for p in r.rule_ids)) lambda r: any(p.location_id.id in parents for p in r.rule_ids)
)
def get_parents(self): def get_parents(self):
location = self.location_id location = self.location_id
@@ -122,54 +137,80 @@ class StockRequest(models.AbstractModel):
result |= location result |= location
return result return result
@api.constrains('company_id', 'product_id', 'warehouse_id', @api.constrains(
'location_id', 'route_id') "company_id", "product_id", "warehouse_id", "location_id", "route_id"
)
def _check_company_constrains(self): def _check_company_constrains(self):
""" Check if the related models have the same company """ """ Check if the related models have the same company """
for rec in self: for rec in self:
if rec.product_id.company_id and \ if (
rec.product_id.company_id != rec.company_id: rec.product_id.company_id
and rec.product_id.company_id != rec.company_id
):
raise ValidationError( raise ValidationError(
_('You have entered a product that is assigned ' _(
'to another company.')) "You have entered a product that is assigned "
if rec.location_id.company_id and \ "to another company."
rec.location_id.company_id != rec.company_id: )
)
if (
rec.location_id.company_id
and rec.location_id.company_id != rec.company_id
):
raise ValidationError( raise ValidationError(
_('You have entered a location that is ' _(
'assigned to another company.')) "You have entered a location that is "
"assigned to another company."
)
)
if rec.warehouse_id.company_id != rec.company_id: if rec.warehouse_id.company_id != rec.company_id:
raise ValidationError( raise ValidationError(
_('You have entered a warehouse that is ' _(
'assigned to another company.')) "You have entered a warehouse that is "
if rec.route_id and rec.route_id.company_id and \ "assigned to another company."
rec.route_id.company_id != rec.company_id: )
)
if (
rec.route_id
and rec.route_id.company_id
and rec.route_id.company_id != rec.company_id
):
raise ValidationError( raise ValidationError(
_('You have entered a route that is ' _(
'assigned to another company.')) "You have entered a route that is "
"assigned to another company."
)
)
@api.constrains('product_id') @api.constrains("product_id")
def _check_product_uom(self): def _check_product_uom(self):
''' Check if the UoM has the same category as the """ Check if the UoM has the same category as the
product standard UoM ''' product standard UoM """
if any(request.product_id.uom_id.category_id != if any(
request.product_uom_id.category_id for request in self): request.product_id.uom_id.category_id != request.product_uom_id.category_id
for request in self
):
raise ValidationError( raise ValidationError(
_('You have to select a product unit of measure in the ' _(
'same category than the default unit ' "You have to select a product unit of measure in the "
'of measure of the product')) "same category than the default unit "
"of measure of the product"
)
)
@api.constrains('product_qty') @api.constrains("product_qty")
def _check_qty(self): def _check_qty(self):
for rec in self: for rec in self:
if rec.product_qty <= 0: if rec.product_qty <= 0:
raise ValueError(_('Stock Request product quantity has to be' raise ValueError(
' strictly positive.')) _("Stock Request product quantity has to be" " strictly positive.")
)
@api.onchange('warehouse_id') @api.onchange("warehouse_id")
def onchange_warehouse_id(self): def onchange_warehouse_id(self):
""" Finds location id for changed warehouse. """ """ Finds location id for changed warehouse. """
res = {'domain': {}} res = {"domain": {}}
if self._name == 'stock.request' and self.order_id: if self._name == "stock.request" and self.order_id:
# When the stock request is created from an order the wh and # When the stock request is created from an order the wh and
# location are taken from the order and we rely on it to change # location are taken from the order and we rely on it to change
# all request associated. Thus, no need to apply # all request associated. Thus, no need to apply
@@ -183,42 +224,41 @@ class StockRequest(models.AbstractModel):
self.company_id = self.warehouse_id.company_id self.company_id = self.warehouse_id.company_id
return res return res
@api.onchange('location_id') @api.onchange("location_id")
def onchange_location_id(self): def onchange_location_id(self):
if self.location_id: if self.location_id:
loc_wh = self.location_id.sudo().get_warehouse() loc_wh = self.location_id.sudo().get_warehouse()
if loc_wh and self.warehouse_id != loc_wh: if loc_wh and self.warehouse_id != loc_wh:
self.warehouse_id = loc_wh self.warehouse_id = loc_wh
self.with_context( self.with_context(no_change_childs=True).onchange_warehouse_id()
no_change_childs=True).onchange_warehouse_id()
@api.onchange('allow_virtual_location') @api.onchange("allow_virtual_location")
def onchange_allow_virtual_location(self): def onchange_allow_virtual_location(self):
if self.allow_virtual_location: if self.allow_virtual_location:
return {'domain': {'location_id': []}} return {"domain": {"location_id": []}}
@api.onchange('company_id') @api.onchange("company_id")
def onchange_company_id(self): def onchange_company_id(self):
""" Sets a default warehouse when the company is changed and limits """ Sets a default warehouse when the company is changed and limits
the user selection of warehouses. """ the user selection of warehouses. """
if self.company_id and ( if self.company_id and (
not self.warehouse_id or not self.warehouse_id or self.warehouse_id.company_id != self.company_id
self.warehouse_id.company_id != self.company_id): ):
self.warehouse_id = self.env['stock.warehouse'].search( self.warehouse_id = self.env["stock.warehouse"].search(
[('company_id', '=', self.company_id.id)], limit=1) [("company_id", "=", self.company_id.id)], limit=1
)
self.onchange_warehouse_id() self.onchange_warehouse_id()
return { return {"domain": {"warehouse_id": [("company_id", "=", self.company_id.id)]}}
'domain': {
'warehouse_id': [('company_id', '=', self.company_id.id)]}}
@api.onchange('product_id') @api.onchange("product_id")
def onchange_product_id(self): def onchange_product_id(self):
res = {'domain': {}} res = {"domain": {}}
if self.product_id: if self.product_id:
self.product_uom_id = self.product_id.uom_id.id self.product_uom_id = self.product_id.uom_id.id
res['domain']['product_uom_id'] = [ res["domain"]["product_uom_id"] = [
('category_id', '=', self.product_id.uom_id.category_id.id)] ("category_id", "=", self.product_id.uom_id.category_id.id)
]
return res return res
res['domain']['product_uom_id'] = [] res["domain"]["product_uom_id"] = []
return res return res

View File

@@ -5,67 +5,84 @@ from odoo import api, fields, models
class StockRequestAllocation(models.Model): class StockRequestAllocation(models.Model):
_name = 'stock.request.allocation' _name = "stock.request.allocation"
_description = 'Stock Request Allocation' _description = "Stock Request Allocation"
stock_request_id = fields.Many2one(string='Stock Request', stock_request_id = fields.Many2one(
comodel_name='stock.request', string="Stock Request",
required=True, ondelete='cascade', comodel_name="stock.request",
) required=True,
company_id = fields.Many2one(string='Company', ondelete="cascade",
comodel_name='res.company', )
readonly=True, company_id = fields.Many2one(
related='stock_request_id.company_id', string="Company",
store=True, comodel_name="res.company",
) readonly=True,
stock_move_id = fields.Many2one(string='Stock Move', related="stock_request_id.company_id",
comodel_name='stock.move', store=True,
required=True, ondelete='cascade', )
) stock_move_id = fields.Many2one(
product_id = fields.Many2one(string='Product', string="Stock Move",
comodel_name='product.product', comodel_name="stock.move",
related='stock_request_id.product_id', required=True,
readonly=True, ondelete="cascade",
) )
product_uom_id = fields.Many2one(string='UoM', comodel_name='uom.uom', product_id = fields.Many2one(
related='stock_request_id.product_uom_id', string="Product",
readonly=True, comodel_name="product.product",
) related="stock_request_id.product_id",
readonly=True,
)
product_uom_id = fields.Many2one(
string="UoM",
comodel_name="uom.uom",
related="stock_request_id.product_uom_id",
readonly=True,
)
requested_product_uom_qty = fields.Float( requested_product_uom_qty = fields.Float(
'Requested Quantity (UoM)', "Requested Quantity (UoM)",
help='Quantity of the stock request allocated to the stock move, ' help="Quantity of the stock request allocated to the stock move, "
'in the UoM of the Stock Request', "in the UoM of the Stock Request",
) )
requested_product_qty = fields.Float( requested_product_qty = fields.Float(
'Requested Quantity', "Requested Quantity",
help='Quantity of the stock request allocated to the stock move, ' help="Quantity of the stock request allocated to the stock move, "
'in the default UoM of the product', "in the default UoM of the product",
compute='_compute_requested_product_qty' compute="_compute_requested_product_qty",
) )
allocated_product_qty = fields.Float( allocated_product_qty = fields.Float(
'Allocated Quantity', "Allocated Quantity",
help='Quantity of the stock request allocated to the stock move, ' help="Quantity of the stock request allocated to the stock move, "
'in the default UoM of the product', "in the default UoM of the product",
)
open_product_qty = fields.Float(
"Open Quantity", compute="_compute_open_product_qty"
) )
open_product_qty = fields.Float('Open Quantity',
compute='_compute_open_product_qty')
@api.depends('stock_request_id.product_id', @api.depends(
'stock_request_id.product_uom_id', "stock_request_id.product_id",
'requested_product_uom_qty') "stock_request_id.product_uom_id",
"requested_product_uom_qty",
)
def _compute_requested_product_qty(self): def _compute_requested_product_qty(self):
for rec in self: for rec in self:
rec.requested_product_qty = rec.product_uom_id._compute_quantity( rec.requested_product_qty = rec.product_uom_id._compute_quantity(
rec.requested_product_uom_qty, rec.product_id.uom_id) rec.requested_product_uom_qty, rec.product_id.uom_id
)
@api.depends('requested_product_qty', 'allocated_product_qty', @api.depends(
'stock_move_id', 'stock_move_id.state') "requested_product_qty",
"allocated_product_qty",
"stock_move_id",
"stock_move_id.state",
)
def _compute_open_product_qty(self): def _compute_open_product_qty(self):
for rec in self: for rec in self:
if rec.stock_move_id.state == 'cancel': if rec.stock_move_id.state == "cancel":
rec.open_product_qty = 0.0 rec.open_product_qty = 0.0
else: else:
rec.open_product_qty = \ rec.open_product_qty = (
rec.requested_product_qty - rec.allocated_product_qty rec.requested_product_qty - rec.allocated_product_qty
)
if rec.open_product_qty < 0.0: if rec.open_product_qty < 0.0:
rec.open_product_qty = 0.0 rec.open_product_qty = 0.0

View File

@@ -1,163 +1,190 @@
# Copyright 2018 Creu Blanca # Copyright 2018 Creu Blanca
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, fields, models, _ from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError, AccessError from odoo.exceptions import AccessError, UserError, ValidationError
class StockRequestOrder(models.Model): class StockRequestOrder(models.Model):
_name = 'stock.request.order' _name = "stock.request.order"
_description = 'Stock Request Order' _description = "Stock Request Order"
_inherit = ['mail.thread', 'mail.activity.mixin'] _inherit = ["mail.thread", "mail.activity.mixin"]
_order = 'id desc' _order = "id desc"
@api.model @api.model
def default_get(self, fields): def default_get(self, fields):
res = super().default_get(fields) res = super().default_get(fields)
warehouse = None warehouse = None
if 'warehouse_id' not in res and res.get('company_id'): if "warehouse_id" not in res and res.get("company_id"):
warehouse = self.env['stock.warehouse'].search( warehouse = self.env["stock.warehouse"].search(
[('company_id', '=', res['company_id'])], limit=1) [("company_id", "=", res["company_id"])], limit=1
)
if warehouse: if warehouse:
res['warehouse_id'] = warehouse.id res["warehouse_id"] = warehouse.id
res['location_id'] = warehouse.lot_stock_id.id res["location_id"] = warehouse.lot_stock_id.id
return res return res
def __get_request_order_states(self): def __get_request_order_states(self):
return self.env['stock.request']._get_request_states() return self.env["stock.request"]._get_request_states()
def _get_request_order_states(self): def _get_request_order_states(self):
return self.__get_request_order_states() return self.__get_request_order_states()
def _get_default_requested_by(self): def _get_default_requested_by(self):
return self.env['res.users'].browse(self.env.uid) return self.env["res.users"].browse(self.env.uid)
name = fields.Char( name = fields.Char(
'Name', copy=False, required=True, readonly=True, "Name",
states={'draft': [('readonly', False)]}, copy=False,
default='/') required=True,
readonly=True,
states={"draft": [("readonly", False)]},
default="/",
)
state = fields.Selection( state = fields.Selection(
selection=_get_request_order_states, selection=_get_request_order_states,
string='Status', copy=False, default='draft', index=True, string="Status",
readonly=True, track_visibility='onchange', copy=False,
default="draft",
index=True,
readonly=True,
track_visibility="onchange",
) )
requested_by = fields.Many2one( requested_by = fields.Many2one(
'res.users', 'Requested by', required=True, "res.users",
track_visibility='onchange', "Requested by",
required=True,
track_visibility="onchange",
default=lambda s: s._get_default_requested_by(), default=lambda s: s._get_default_requested_by(),
) )
warehouse_id = fields.Many2one( warehouse_id = fields.Many2one(
'stock.warehouse', 'Warehouse', readonly=True, "stock.warehouse",
ondelete="cascade", required=True, "Warehouse",
states={'draft': [('readonly', False)]}) readonly=True,
ondelete="cascade",
required=True,
states={"draft": [("readonly", False)]},
)
location_id = fields.Many2one( location_id = fields.Many2one(
'stock.location', 'Location', readonly=True, "stock.location",
domain=[('usage', 'in', ['internal', 'transit'])], "Location",
ondelete="cascade", required=True, readonly=True,
states={'draft': [('readonly', False)]}, domain=[("usage", "in", ["internal", "transit"])],
ondelete="cascade",
required=True,
states={"draft": [("readonly", False)]},
) )
allow_virtual_location = fields.Boolean( allow_virtual_location = fields.Boolean(
related='company_id.stock_request_allow_virtual_loc', related="company_id.stock_request_allow_virtual_loc", readonly=True
readonly=True,
) )
procurement_group_id = fields.Many2one( procurement_group_id = fields.Many2one(
'procurement.group', 'Procurement Group', readonly=True, "procurement.group",
states={'draft': [('readonly', False)]}, "Procurement Group",
readonly=True,
states={"draft": [("readonly", False)]},
help="Moves created through this stock request will be put in this " help="Moves created through this stock request will be put in this "
"procurement group. If none is given, the moves generated by " "procurement group. If none is given, the moves generated by "
"procurement rules will be grouped into one big picking.", "procurement rules will be grouped into one big picking.",
) )
company_id = fields.Many2one( company_id = fields.Many2one(
'res.company', 'Company', required=True, readonly=True, "res.company",
states={'draft': [('readonly', False)]}, "Company",
default=lambda self: self.env['res.company']._company_default_get( required=True,
'stock.request.order'), readonly=True,
states={"draft": [("readonly", False)]},
default=lambda self: self.env["res.company"]._company_default_get(
"stock.request.order"
),
) )
expected_date = fields.Datetime( expected_date = fields.Datetime(
'Expected Date', default=fields.Datetime.now, index=True, "Expected Date",
required=True, readonly=True, default=fields.Datetime.now,
states={'draft': [('readonly', False)]}, index=True,
required=True,
readonly=True,
states={"draft": [("readonly", False)]},
help="Date when you expect to receive the goods.", help="Date when you expect to receive the goods.",
) )
picking_policy = fields.Selection([ picking_policy = fields.Selection(
('direct', 'Receive each product when available'), [
('one', 'Receive all products at once')], ("direct", "Receive each product when available"),
string='Shipping Policy', required=True, readonly=True, ("one", "Receive all products at once"),
states={'draft': [('readonly', False)]}, ],
default='direct', string="Shipping Policy",
required=True,
readonly=True,
states={"draft": [("readonly", False)]},
default="direct",
)
move_ids = fields.One2many(
comodel_name="stock.move",
compute="_compute_move_ids",
string="Stock Moves",
readonly=True,
)
picking_ids = fields.One2many(
"stock.picking",
compute="_compute_picking_ids",
string="Pickings",
readonly=True,
)
picking_count = fields.Integer(
string="Delivery Orders", compute="_compute_picking_ids", readonly=True
) )
move_ids = fields.One2many(comodel_name='stock.move',
compute='_compute_move_ids',
string='Stock Moves', readonly=True,
)
picking_ids = fields.One2many('stock.picking',
compute='_compute_picking_ids',
string='Pickings', readonly=True,
)
picking_count = fields.Integer(string='Delivery Orders',
compute='_compute_picking_ids',
readonly=True,
)
stock_request_ids = fields.One2many( stock_request_ids = fields.One2many(
'stock.request', "stock.request", inverse_name="order_id", copy=True
inverse_name='order_id',
copy=True,
) )
stock_request_count = fields.Integer( stock_request_count = fields.Integer(
string='Stock requests', string="Stock requests", compute="_compute_stock_request_count", readonly=True
compute='_compute_stock_request_count',
readonly=True,
) )
_sql_constraints = [ _sql_constraints = [
('name_uniq', 'unique(name, company_id)', ("name_uniq", "unique(name, company_id)", "Stock Request name must be unique")
'Stock Request name must be unique'),
] ]
@api.depends('stock_request_ids.allocation_ids') @api.depends("stock_request_ids.allocation_ids")
def _compute_picking_ids(self): def _compute_picking_ids(self):
for record in self: for record in self:
record.picking_ids = record.stock_request_ids.mapped('picking_ids') record.picking_ids = record.stock_request_ids.mapped("picking_ids")
record.picking_count = len(record.picking_ids) record.picking_count = len(record.picking_ids)
@api.depends('stock_request_ids') @api.depends("stock_request_ids")
def _compute_move_ids(self): def _compute_move_ids(self):
for record in self: for record in self:
record.move_ids = record.stock_request_ids.mapped('move_ids') record.move_ids = record.stock_request_ids.mapped("move_ids")
@api.depends('stock_request_ids') @api.depends("stock_request_ids")
def _compute_stock_request_count(self): def _compute_stock_request_count(self):
for record in self: for record in self:
record.stock_request_count = len(record.stock_request_ids) record.stock_request_count = len(record.stock_request_ids)
@api.onchange('requested_by') @api.onchange("requested_by")
def onchange_requested_by(self): def onchange_requested_by(self):
self.change_childs() self.change_childs()
@api.onchange('expected_date') @api.onchange("expected_date")
def onchange_expected_date(self): def onchange_expected_date(self):
self.change_childs() self.change_childs()
@api.onchange('picking_policy') @api.onchange("picking_policy")
def onchange_picking_policy(self): def onchange_picking_policy(self):
self.change_childs() self.change_childs()
@api.onchange('location_id') @api.onchange("location_id")
def onchange_location_id(self): def onchange_location_id(self):
if self.location_id: if self.location_id:
loc_wh = self.location_id.sudo().get_warehouse() loc_wh = self.location_id.sudo().get_warehouse()
if loc_wh and self.warehouse_id != loc_wh: if loc_wh and self.warehouse_id != loc_wh:
self.warehouse_id = loc_wh self.warehouse_id = loc_wh
self.with_context( self.with_context(no_change_childs=True).onchange_warehouse_id()
no_change_childs=True).onchange_warehouse_id()
self.change_childs() self.change_childs()
@api.onchange('allow_virtual_location') @api.onchange("allow_virtual_location")
def onchange_allow_virtual_location(self): def onchange_allow_virtual_location(self):
if self.allow_virtual_location: if self.allow_virtual_location:
return {'domain': {'location_id': []}} return {"domain": {"location_id": []}}
@api.onchange('warehouse_id') @api.onchange("warehouse_id")
def onchange_warehouse_id(self): def onchange_warehouse_id(self):
if self.warehouse_id: if self.warehouse_id:
# search with sudo because the user may not have permissions # search with sudo because the user may not have permissions
@@ -170,26 +197,25 @@ class StockRequestOrder(models.Model):
self.with_context(no_change_childs=True).onchange_company_id() self.with_context(no_change_childs=True).onchange_company_id()
self.change_childs() self.change_childs()
@api.onchange('procurement_group_id') @api.onchange("procurement_group_id")
def onchange_procurement_group_id(self): def onchange_procurement_group_id(self):
self.change_childs() self.change_childs()
@api.onchange('company_id') @api.onchange("company_id")
def onchange_company_id(self): def onchange_company_id(self):
if self.company_id and ( if self.company_id and (
not self.warehouse_id or not self.warehouse_id
self.warehouse_id.sudo().company_id != self.company_id or self.warehouse_id.sudo().company_id != self.company_id
): ):
self.warehouse_id = self.env['stock.warehouse'].search( self.warehouse_id = self.env["stock.warehouse"].search(
[('company_id', '=', self.company_id.id)], limit=1) [("company_id", "=", self.company_id.id)], limit=1
)
self.with_context(no_change_childs=True).onchange_warehouse_id() self.with_context(no_change_childs=True).onchange_warehouse_id()
self.change_childs() self.change_childs()
return { return {"domain": {"warehouse_id": [("company_id", "=", self.company_id.id)]}}
'domain': {
'warehouse_id': [('company_id', '=', self.company_id.id)]}}
def change_childs(self): def change_childs(self):
if not self._context.get('no_change_childs', False): if not self._context.get("no_change_childs", False):
for line in self.stock_request_ids: for line in self.stock_request_ids:
line.warehouse_id = self.warehouse_id line.warehouse_id = self.warehouse_id
line.location_id = self.location_id line.location_id = self.location_id
@@ -203,121 +229,142 @@ class StockRequestOrder(models.Model):
def action_confirm(self): def action_confirm(self):
for line in self.stock_request_ids: for line in self.stock_request_ids:
line.action_confirm() line.action_confirm()
self.state = 'open' self.state = "open"
return True return True
def action_draft(self): def action_draft(self):
for line in self.stock_request_ids: for line in self.stock_request_ids:
line.action_draft() line.action_draft()
self.state = 'draft' self.state = "draft"
return True return True
def action_cancel(self): def action_cancel(self):
for line in self.stock_request_ids: for line in self.stock_request_ids:
line.action_cancel() line.action_cancel()
self.state = 'cancel' self.state = "cancel"
return True return True
def action_done(self): def action_done(self):
self.state = 'done' self.state = "done"
return True return True
def check_done(self): def check_done(self):
if not self.stock_request_ids.filtered(lambda r: r.state != 'done'): if not self.stock_request_ids.filtered(lambda r: r.state != "done"):
self.action_done() self.action_done()
return return
@api.multi @api.multi
def action_view_transfer(self): def action_view_transfer(self):
action = self.env.ref('stock.action_picking_tree_all').read()[0] action = self.env.ref("stock.action_picking_tree_all").read()[0]
pickings = self.mapped('picking_ids') pickings = self.mapped("picking_ids")
if len(pickings) > 1: if len(pickings) > 1:
action['domain'] = [('id', 'in', pickings.ids)] action["domain"] = [("id", "in", pickings.ids)]
elif pickings: elif pickings:
action['views'] = [ action["views"] = [(self.env.ref("stock.view_picking_form").id, "form")]
(self.env.ref('stock.view_picking_form').id, 'form')] action["res_id"] = pickings.id
action['res_id'] = pickings.id
return action return action
@api.multi @api.multi
def action_view_stock_requests(self): def action_view_stock_requests(self):
action = self.env.ref( action = self.env.ref("stock_request.action_stock_request_form").read()[0]
'stock_request.action_stock_request_form').read()[0]
if len(self.stock_request_ids) > 1: if len(self.stock_request_ids) > 1:
action['domain'] = [('order_id', 'in', self.ids)] action["domain"] = [("order_id", "in", self.ids)]
elif self.stock_request_ids: elif self.stock_request_ids:
action['views'] = [ action["views"] = [
(self.env.ref( (self.env.ref("stock_request.view_stock_request_form").id, "form")
'stock_request.view_stock_request_form').id, 'form')] ]
action['res_id'] = self.stock_request_ids.id action["res_id"] = self.stock_request_ids.id
return action return action
@api.model @api.model
def create(self, vals): def create(self, vals):
upd_vals = vals.copy() upd_vals = vals.copy()
if upd_vals.get('name', '/') == '/': if upd_vals.get("name", "/") == "/":
upd_vals['name'] = self.env['ir.sequence'].next_by_code( upd_vals["name"] = self.env["ir.sequence"].next_by_code(
'stock.request.order') "stock.request.order"
)
return super().create(upd_vals) return super().create(upd_vals)
@api.multi @api.multi
def unlink(self): def unlink(self):
if self.filtered(lambda r: r.state != 'draft'): if self.filtered(lambda r: r.state != "draft"):
raise UserError(_('Only orders on draft state can be unlinked')) raise UserError(_("Only orders on draft state can be unlinked"))
return super().unlink() return super().unlink()
@api.constrains('warehouse_id', 'company_id') @api.constrains("warehouse_id", "company_id")
def _check_warehouse_company(self): def _check_warehouse_company(self):
if any(request.warehouse_id.company_id != if any(
request.company_id for request in self): request.warehouse_id.company_id != request.company_id for request in self
):
raise ValidationError( raise ValidationError(
_('The company of the stock request must match with ' _(
'that of the warehouse.')) "The company of the stock request must match with "
"that of the warehouse."
)
)
@api.constrains('location_id', 'company_id') @api.constrains("location_id", "company_id")
def _check_location_company(self): def _check_location_company(self):
if any(request.location_id.company_id and if any(
request.location_id.company_id != request.location_id.company_id
request.company_id for request in self): and request.location_id.company_id != request.company_id
for request in self
):
raise ValidationError( raise ValidationError(
_('The company of the stock request must match with ' _(
'that of the location.')) "The company of the stock request must match with "
"that of the location."
)
)
@api.model @api.model
def _create_from_product_multiselect(self, products): def _create_from_product_multiselect(self, products):
if not products: if not products:
return False return False
if products._name not in ('product.product', 'product.template'): if products._name not in ("product.product", "product.template"):
raise ValidationError( raise ValidationError(
_("This action only works in the context of products")) _("This action only works in the context of products")
if products._name == 'product.template': )
if products._name == "product.template":
# search instead of mapped so we don't include archived variants # search instead of mapped so we don't include archived variants
products = self.env['product.product'].search([ products = self.env["product.product"].search(
('product_tmpl_id', 'in', products.ids) [("product_tmpl_id", "in", products.ids)]
]) )
expected = self.default_get(['expected_date'])['expected_date'] expected = self.default_get(["expected_date"])["expected_date"]
try: try:
order = self.env['stock.request.order'].create(dict( order = self.env["stock.request.order"].create(
expected_date=expected, dict(
stock_request_ids=[(0, 0, dict(
product_id=product.id,
product_uom_id=product.uom_id.id,
product_uom_qty=1.0,
expected_date=expected, expected_date=expected,
)) for product in products] stock_request_ids=[
)) (
0,
0,
dict(
product_id=product.id,
product_uom_id=product.uom_id.id,
product_uom_qty=1.0,
expected_date=expected,
),
)
for product in products
],
)
)
except AccessError: except AccessError:
# TODO: if there is a nice way to hide the action from the # TODO: if there is a nice way to hide the action from the
# Action-menu if the user doesn't have the necessary rights, # Action-menu if the user doesn't have the necessary rights,
# that would be a better way of doing this # that would be a better way of doing this
raise UserError(_( raise UserError(
"Unfortunately it seems you do not have the necessary rights " _(
"for creating stock requests. Please contact your " "Unfortunately it seems you do not have the necessary rights "
"administrator.")) "for creating stock requests. Please contact your "
action = self.env.ref('stock_request.stock_request_order_action' "administrator."
).read()[0] )
action['views'] = [( )
self.env.ref('stock_request.stock_request_order_form').id, 'form')] action = self.env.ref("stock_request.stock_request_order_action").read()[0]
action['res_id'] = order.id action["views"] = [
(self.env.ref("stock_request.stock_request_order_form").id, "form")
]
action["res_id"] = order.id
return action return action

View File

@@ -5,16 +5,38 @@ from odoo import models
class StockRule(models.Model): class StockRule(models.Model):
_inherit = 'stock.rule' _inherit = "stock.rule"
def _get_stock_move_values(self, product_id, product_qty, product_uom, def _get_stock_move_values(
location_id, name, origin, values, group_id): self,
product_id,
product_qty,
product_uom,
location_id,
name,
origin,
values,
group_id,
):
result = super(StockRule, self)._get_stock_move_values( result = super(StockRule, self)._get_stock_move_values(
product_id, product_qty, product_uom, product_id,
location_id, name, origin, values, group_id) product_qty,
if values.get('stock_request_id', False): product_uom,
result['allocation_ids'] = [(0, 0, { location_id,
'stock_request_id': values.get('stock_request_id'), name,
'requested_product_uom_qty': product_qty, origin,
})] values,
group_id,
)
if values.get("stock_request_id", False):
result["allocation_ids"] = [
(
0,
0,
{
"stock_request_id": values.get("stock_request_id"),
"requested_product_uom_qty": product_qty,
},
)
]
return result return result

View File

@@ -1,28 +1,46 @@
# Copyright 2018 Eficent Business and IT Consulting Services, S.L. # Copyright 2018 Eficent Business and IT Consulting Services, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, models, _ from odoo import _, api, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
class StockWarehouse(models.Model): class StockWarehouse(models.Model):
_inherit = 'stock.warehouse' _inherit = "stock.warehouse"
@api.constrains('company_id') @api.constrains("company_id")
def _check_company_stock_request(self): def _check_company_stock_request(self):
if any(self.env['stock.request'].search( if any(
[('company_id', '!=', rec.company_id.id), self.env["stock.request"].search(
('warehouse_id', '=', rec.id)], limit=1) [
for rec in self): ("company_id", "!=", rec.company_id.id),
("warehouse_id", "=", rec.id),
],
limit=1,
)
for rec in self
):
raise ValidationError( raise ValidationError(
_('You cannot change the company of the warehouse, as it is ' _(
'already assigned to stock requests that belong to ' "You cannot change the company of the warehouse, as it is "
'another company.')) "already assigned to stock requests that belong to "
if any(self.env['stock.request.order'].search( "another company."
[('company_id', '!=', rec.company_id.id), )
('warehouse_id', '=', rec.id)], limit=1) )
for rec in self): if any(
self.env["stock.request.order"].search(
[
("company_id", "!=", rec.company_id.id),
("warehouse_id", "=", rec.id),
],
limit=1,
)
for rec in self
):
raise ValidationError( raise ValidationError(
_('You cannot change the company of the warehouse, as it is ' _(
'already assigned to stock request orders that belong to ' "You cannot change the company of the warehouse, as it is "
'another company.')) "already assigned to stock request orders that belong to "
"another company."
)
)

View File

@@ -39,7 +39,7 @@
<field name="global" eval="True"/> <field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field> <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record> </record>
<record id="stock_request_followers_rule" model="ir.rule"> <record id="stock_request_followers_rule" model="ir.rule">
<field name="name">Follow Stock Request</field> <field name="name">Follow Stock Request</field>
<field name="model_id" ref="model_stock_request"/> <field name="model_id" ref="model_stock_request"/>
@@ -51,7 +51,7 @@
<field name="domain_force">['|',('requested_by','=',user.id), <field name="domain_force">['|',('requested_by','=',user.id),
('message_partner_ids', 'in', [user.partner_id.id])]</field> ('message_partner_ids', 'in', [user.partner_id.id])]</field>
</record> </record>
<record id="stock_request_rule" model="ir.rule"> <record id="stock_request_rule" model="ir.rule">
<field name="name">Stock Request User</field> <field name="name">Stock Request User</field>
<field name="model_id" ref="model_stock_request"/> <field name="model_id" ref="model_stock_request"/>
@@ -62,7 +62,7 @@
<field name="perm_unlink" eval="True"/> <field name="perm_unlink" eval="True"/>
<field name="domain_force">[('requested_by','=',user.id)]</field> <field name="domain_force">[('requested_by','=',user.id)]</field>
</record> </record>
<record id="stock_request_manager_rule" model="ir.rule"> <record id="stock_request_manager_rule" model="ir.rule">
<field name="name">Stock Request Manager</field> <field name="name">Stock Request Manager</field>
<field name="model_id" ref="model_stock_request"/> <field name="model_id" ref="model_stock_request"/>

View File

@@ -1,2 +1 @@
from . import test_stock_request from . import test_stock_request

File diff suppressed because it is too large Load Diff