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