Merge PR #1134 into 14.0

Signed-off-by ps-tubtim
This commit is contained in:
OCA-git-bot
2021-04-22 09:06:34 +00:00
67 changed files with 33109 additions and 0 deletions

View File

@@ -0,0 +1 @@
../../../../stock_request

View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

135
stock_request/README.rst Normal file
View File

@@ -0,0 +1,135 @@
=============
Stock Request
=============
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github
:target: https://github.com/OCA/stock-logistics-warehouse/tree/13.0/stock_request
:alt: OCA/stock-logistics-warehouse
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-13-0/stock-logistics-warehouse-13-0-stock_request
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/153/13.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
This module was written to allow users to request products that are
frequently stocked by the company, to be transferred to their chosen location.
**Table of contents**
.. contents::
:local:
Configuration
=============
To configure this module:
* Go to Stock Requests > Settings
Users should be assigned to the groups 'Stock Request / User' or 'Stock
Request / Manager'.
## Group Stock Request / User
* Can see her/his own Stock Requests, and others that she/he's been granted
permission to follow.
* Can create/update only her/his Stock Requests.
## Group Stock Request / Manager
* Can fully manage all Stock Requests
Usage
=====
## Creation
* Go to 'Stock Requests / Stock Requests' and create a new Request.
* Indicate a product, quantity and location.
* Press 'Confirm'.
Upon confirmation the request will be evaluated using the procurement rules
for the selected location.
In case that transfers are created, the user will be able to access to them
from the button 'Transfers' available in the Stock Request.
## Cancel
When the user cancels a Stock Request, the related pending stock moves will be
also cancelled.
Known issues / Roadmap
======================
There is no way to achieve Storck Request and Stock Request Orders.
It should be developed taking into account that only Cancel and Done
stock request can be archived.
It is also required to manage active field logically from Orders to SRs.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/stock-logistics-warehouse/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/stock-logistics-warehouse/issues/new?body=module:%20stock_request%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* ForgeFlow
Contributors
~~~~~~~~~~~~
* Jordi Ballester (EFICENT) <jordi.ballester@forgeflow.com>.
* Enric Tobella <etobella@creublanca.es>
* Atte Isopuro <atte.isopuro@avoin.systems>
* Lois Rilo <lois.rilo@forgeflow.com>
* Raul Martin <raul.martin@braintec-group.com>
* Serpent Consulting Services Pvt. Ltd. <support@serpentcs.com>
* `Open Source Integrators <https://www.opensourceintegrators.com>`_
* Maxime Chambreuil <mchambreuil@opensourceintegrators.com>
* Steve Campbell <scampbell@opensourceintegrators.com>
* Héctor Villarreal <hector.villarreal@forgeflow.com>
Maintainers
~~~~~~~~~~~
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/stock-logistics-warehouse <https://github.com/OCA/stock-logistics-warehouse/tree/13.0/stock_request>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

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

View File

@@ -0,0 +1,27 @@
# Copyright 2017-2020 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
{
"name": "Stock Request",
"summary": "Internal request for stock",
"version": "14.0.1.0.0",
"license": "LGPL-3",
"website": "https://github.com/OCA/stock-logistics-warehouse",
"author": "ForgeFlow, Odoo Community Association (OCA)",
"category": "Warehouse Management",
"depends": ["stock"],
"data": [
"security/stock_request_security.xml",
"security/ir.model.access.csv",
"views/product.xml",
"views/stock_request_views.xml",
"views/stock_request_allocation_views.xml",
"views/stock_move_views.xml",
"views/stock_picking_views.xml",
"views/stock_request_order_views.xml",
"views/res_config_settings_views.xml",
"views/stock_request_menu.xml",
"data/stock_request_sequence_data.xml",
],
"installable": True,
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="seq_stock_request" model="ir.sequence">
<field name="name">Stock Request</field>
<field name="code">stock.request</field>
<field name="prefix">SR/</field>
<field name="padding">5</field>
<field name="company_id" eval="False" />
</record>
<record id="seq_stock_request_order" model="ir.sequence">
<field name="name">Stock Request Order</field>
<field name="code">stock.request.order</field>
<field name="prefix">SRO/</field>
<field name="padding">5</field>
<field name="company_id" eval="False" />
</record>
</odoo>

1096
stock_request/i18n/ca.po Normal file

File diff suppressed because it is too large Load Diff

1142
stock_request/i18n/de.po Normal file

File diff suppressed because it is too large Load Diff

1097
stock_request/i18n/el_GR.po Normal file

File diff suppressed because it is too large Load Diff

1157
stock_request/i18n/es.po Normal file

File diff suppressed because it is too large Load Diff

1097
stock_request/i18n/es_ES.po Normal file

File diff suppressed because it is too large Load Diff

1096
stock_request/i18n/eu.po Normal file

File diff suppressed because it is too large Load Diff

1096
stock_request/i18n/fi.po Normal file

File diff suppressed because it is too large Load Diff

1096
stock_request/i18n/fr.po Normal file

File diff suppressed because it is too large Load Diff

1097
stock_request/i18n/fr_CH.po Normal file

File diff suppressed because it is too large Load Diff

1097
stock_request/i18n/fr_FR.po Normal file

File diff suppressed because it is too large Load Diff

1096
stock_request/i18n/gl.po Normal file

File diff suppressed because it is too large Load Diff

1097
stock_request/i18n/hr.po Normal file

File diff suppressed because it is too large Load Diff

1098
stock_request/i18n/hr_HR.po Normal file

File diff suppressed because it is too large Load Diff

1096
stock_request/i18n/it.po Normal file

File diff suppressed because it is too large Load Diff

1096
stock_request/i18n/nl.po Normal file

File diff suppressed because it is too large Load Diff

1097
stock_request/i18n/nl_NL.po Normal file

File diff suppressed because it is too large Load Diff

1096
stock_request/i18n/pt.po Normal file

File diff suppressed because it is too large Load Diff

1097
stock_request/i18n/pt_BR.po Normal file

File diff suppressed because it is too large Load Diff

1097
stock_request/i18n/ro.po Normal file

File diff suppressed because it is too large Load Diff

1098
stock_request/i18n/ru.po Normal file

File diff suppressed because it is too large Load Diff

1097
stock_request/i18n/sl.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1096
stock_request/i18n/tr.po Normal file

File diff suppressed because it is too large Load Diff

1097
stock_request/i18n/tr_TR.po Normal file

File diff suppressed because it is too large Load Diff

1097
stock_request/i18n/vi_VN.po Normal file

File diff suppressed because it is too large Load Diff

1121
stock_request/i18n/zh_CN.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
from . import procurement_group
from . import stock_request_abstract
from . import stock_request
from . import stock_request_allocation
from . import stock_request_order
from . import stock_move
from . import stock_picking
from . import stock_rule
from . import stock_move_line
from . import res_config_settings
from . import stock_warehouse
from . import stock_location
from . import stock_location_route
from . import res_company

View File

@@ -0,0 +1,29 @@
# Copyright (C) 2019 Open Source Integrators
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo import api, models
class ProcurementGroup(models.Model):
_inherit = "procurement.group"
@api.model
def run(self, procurements, raise_user_error=True):
indexes_to_pop = []
new_procs = []
for i, procurement in enumerate(procurements):
if "stock_request_id" in procurement.values and procurement.values.get(
"stock_request_id"
):
req = self.env["stock.request"].browse(
procurement.values.get("stock_request_id")
)
if req.order_id:
new_procs.append(procurement._replace(origin=req.order_id.name))
indexes_to_pop.append(i)
if new_procs:
indexes_to_pop.reverse()
for index in indexes_to_pop:
procurements.pop(index)
procurements.extend(new_procs)
return super().run(procurements, raise_user_error=raise_user_error)

View File

@@ -0,0 +1,14 @@
# Copyright 2018 ForgeFlow S.L.
# (http://www.forgeflow.com)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import fields, models
class ResCompany(models.Model):
_inherit = "res.company"
stock_request_allow_virtual_loc = fields.Boolean(
string="Allow Virtual locations on Stock Requests"
)

View File

@@ -0,0 +1,40 @@
# Copyright 2018 Creu Blanca
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, fields, models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
group_stock_request_order = fields.Boolean(
implied_group="stock_request.group_stock_request_order"
)
module_stock_request_purchase = fields.Boolean(
string="Stock Requests for Purchases"
)
module_stock_request_kanban = fields.Boolean(
string="Stock Requests Kanban integration"
)
stock_request_allow_virtual_loc = fields.Boolean(
related="company_id.stock_request_allow_virtual_loc", readonly=False
)
module_stock_request_analytic = fields.Boolean(
string="Stock Requests Analytic integration"
)
module_stock_request_submit = fields.Boolean(
string="Submitted state in Stock Requests"
)
module_stock_request_mrp = fields.Boolean(string="Stock Request for Manufacturing")
# Dependencies
@api.onchange("stock_request_allow_virtual_loc")
def _onchange_stock_request_allow_virtual_loc(self):
if self.stock_request_allow_virtual_loc:
self.group_stock_multi_locations = True

View File

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

View File

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

View File

@@ -0,0 +1,78 @@
# Copyright 2017-2020 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class StockMove(models.Model):
_inherit = "stock.move"
allocation_ids = fields.One2many(
comodel_name="stock.request.allocation",
inverse_name="stock_move_id",
string="Stock Request Allocation",
)
stock_request_ids = fields.One2many(
comodel_name="stock.request",
string="Stock Requests",
compute="_compute_stock_request_ids",
)
@api.depends("allocation_ids")
def _compute_stock_request_ids(self):
for rec in self:
rec.stock_request_ids = rec.allocation_ids.mapped("stock_request_id")
def _merge_moves_fields(self):
res = super(StockMove, self)._merge_moves_fields()
res["allocation_ids"] = [(4, m.id) for m in self.mapped("allocation_ids")]
return res
@api.constrains("company_id")
def _check_company_stock_request(self):
if any(
self.env["stock.request.allocation"].search(
[
("company_id", "!=", rec.company_id.id),
("stock_move_id", "=", rec.id),
],
limit=1,
)
for rec in self
):
raise ValidationError(
_(
"The company of the stock request must match with "
"that of the location."
)
)
def copy_data(self, default=None):
if not default:
default = {}
if "allocation_ids" not in default:
default["allocation_ids"] = []
for alloc in self.allocation_ids:
default["allocation_ids"].append(
(
0,
0,
{
"stock_request_id": alloc.stock_request_id.id,
"requested_product_uom_qty": alloc.requested_product_uom_qty,
},
)
)
return super(StockMove, self).copy_data(default)
def _action_cancel(self):
res = super()._action_cancel()
self.mapped("allocation_ids.stock_request_id").check_done()
return res
def _action_done(self, cancel_backorder=False):
res = super()._action_done(cancel_backorder=cancel_backorder)
self.mapped("allocation_ids.stock_request_id").check_done()
return res

View File

@@ -0,0 +1,70 @@
# Copyright 2017 ForgeFlow S.L.
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0).
from odoo import _, api, models
class StockMoveLine(models.Model):
_inherit = "stock.move.line"
@api.model
def _stock_request_confirm_done_message_content(self, message_data):
title = _("Receipt confirmation %s for your Request %s") % (
message_data["picking_name"],
message_data["request_name"],
)
message = "<h3>%s</h3>" % title
message += _(
"The following requested items from Stock Request %s "
"have now been received in %s using Picking %s:"
) % (
message_data["request_name"],
message_data["location_name"],
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
def _prepare_message_data(self, ml, request, allocated_qty):
return {
"request_name": request.name,
"picking_name": ml.picking_id.name,
"product_name": ml.product_id.name_get()[0][1],
"product_qty": allocated_qty,
"product_uom": ml.product_uom_id.name,
"location_name": ml.location_dest_id.name_get()[0][1],
}
def _action_done(self):
res = super(StockMoveLine, self)._action_done()
for ml in self.filtered(lambda m: m.exists() and m.move_id.allocation_ids):
qty_done = ml.product_uom_id._compute_quantity(
ml.qty_done, ml.product_id.uom_id
)
# We do sudo because potentially the user that completes the move
# may not have permissions for stock.request.
to_allocate_qty = qty_done
for allocation in ml.move_id.allocation_ids.sudo():
allocated_qty = 0.0
if allocation.open_product_qty:
allocated_qty = min(allocation.open_product_qty, to_allocate_qty)
allocation.allocated_product_qty += allocated_qty
to_allocate_qty -= allocated_qty
if allocated_qty:
request = allocation.stock_request_id
message_data = self._prepare_message_data(
ml, request, allocated_qty
)
message = self._stock_request_confirm_done_message_content(
message_data
)
request.message_post(body=message, subtype_xmlid="mail.mt_comment")
request.check_done()
return res

View File

@@ -0,0 +1,39 @@
# Copyright 2017-2020 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, fields, models
class StockPicking(models.Model):
_inherit = "stock.picking"
stock_request_ids = fields.One2many(
comodel_name="stock.request",
string="Stock Requests",
compute="_compute_stock_request_ids",
)
stock_request_count = fields.Integer(
"Stock Request #", compute="_compute_stock_request_ids"
)
@api.depends("move_lines")
def _compute_stock_request_ids(self):
for rec in self:
rec.stock_request_ids = rec.move_lines.mapped("stock_request_ids")
rec.stock_request_count = len(rec.stock_request_ids)
def action_view_stock_request(self):
"""
:return dict: dictionary value for created view
"""
action = self.env.ref("stock_request.action_stock_request_form").read()[0]
requests = self.mapped("stock_request_ids")
if len(requests) > 1:
action["domain"] = [("id", "in", requests.ids)]
elif requests:
action["views"] = [
(self.env.ref("stock_request.view_stock_request_form").id, "form")
]
action["res_id"] = requests.id
return action

View File

@@ -0,0 +1,379 @@
# Copyright 2017-2020 ForgeFlow, S.L.
# 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
from odoo.tools import float_compare
REQUEST_STATES = [
("draft", "Draft"),
("open", "In progress"),
("done", "Done"),
("cancel", "Cancelled"),
]
class StockRequest(models.Model):
_name = "stock.request"
_description = "Stock Request"
_inherit = "stock.request.abstract"
_order = "id desc"
def __get_request_states(self):
return REQUEST_STATES
def _get_request_states(self):
return self.__get_request_states()
def _get_default_requested_by(self):
return self.env["res.users"].browse(self.env.uid)
@staticmethod
def _get_expected_date():
return fields.Datetime.now()
def _get_default_expected_date(self):
if self.order_id:
res = self.order_id.expected_date
else:
res = self._get_expected_date()
return res
name = fields.Char(states={"draft": [("readonly", False)]})
state = fields.Selection(
selection=_get_request_states,
string="Status",
copy=False,
default="draft",
index=True,
readonly=True,
tracking=True,
)
requested_by = fields.Many2one(
"res.users",
"Requested by",
required=True,
tracking=True,
default=lambda s: s._get_default_requested_by(),
)
expected_date = fields.Datetime(
"Expected Date",
default=lambda s: s._get_default_expected_date(),
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,
)
qty_in_progress = fields.Float(
"Qty In Progress",
digits="Product Unit of Measure",
readonly=True,
compute="_compute_qty",
store=True,
help="Quantity in progress.",
)
qty_done = fields.Float(
"Qty Done",
digits="Product Unit of Measure",
readonly=True,
compute="_compute_qty",
store=True,
help="Quantity completed",
)
qty_cancelled = fields.Float(
"Qty Cancelled",
digits="Product Unit of Measure",
readonly=True,
compute="_compute_qty",
store=True,
help="Quantity cancelled",
)
picking_count = fields.Integer(
string="Delivery Orders",
compute="_compute_picking_ids",
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(
states={"draft": [("readonly", False)]}, readonly=True
)
location_id = fields.Many2one(
states={"draft": [("readonly", False)]}, readonly=True
)
product_id = fields.Many2one(states={"draft": [("readonly", False)]}, readonly=True)
product_uom_id = fields.Many2one(
states={"draft": [("readonly", False)]}, readonly=True
)
product_uom_qty = fields.Float(
states={"draft": [("readonly", False)]}, readonly=True
)
procurement_group_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 = [
("name_uniq", "unique(name, company_id)", "Stock Request name must be unique")
]
@api.depends("allocation_ids", "allocation_ids.stock_move_id")
def _compute_move_ids(self):
for request in self:
request.move_ids = request.allocation_ids.mapped("stock_move_id")
@api.depends(
"allocation_ids",
"allocation_ids.stock_move_id",
"allocation_ids.stock_move_id.picking_id",
)
def _compute_picking_ids(self):
for request in self:
request.picking_count = 0
request.picking_ids = self.env["stock.picking"]
request.picking_ids = request.move_ids.filtered(
lambda m: m.state != "cancel"
).mapped("picking_id")
request.picking_count = len(request.picking_ids)
@api.depends(
"allocation_ids",
"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):
for request in self:
incoming_qty = 0.0
other_qty = 0.0
for allocation in request.allocation_ids:
if allocation.stock_move_id.picking_code == "incoming":
incoming_qty += allocation.allocated_product_qty
else:
other_qty += allocation.allocated_product_qty
done_qty = abs(other_qty - incoming_qty)
open_qty = sum(request.allocation_ids.mapped("open_product_qty"))
uom = request.product_id.uom_id
request.qty_done = uom._compute_quantity(done_qty, request.product_uom_id)
request.qty_in_progress = uom._compute_quantity(
open_qty, request.product_uom_id
)
request.qty_cancelled = (
max(
0,
uom._compute_quantity(
request.product_qty - done_qty - open_qty,
request.product_uom_id,
),
)
if request.allocation_ids
else 0
)
@api.constrains("order_id", "requested_by")
def check_order_requested_by(self):
if self.order_id and self.order_id.requested_by != self.requested_by:
raise ValidationError(_("Requested by must be equal to the order"))
@api.constrains("order_id", "warehouse_id")
def check_order_warehouse_id(self):
if self.order_id and self.order_id.warehouse_id != self.warehouse_id:
raise ValidationError(_("Warehouse must be equal to the order"))
@api.constrains("order_id", "location_id")
def check_order_location(self):
if self.order_id and self.order_id.location_id != self.location_id:
raise ValidationError(_("Location must be equal to the order"))
@api.constrains("order_id", "procurement_group_id")
def check_order_procurement_group(self):
if (
self.order_id
and self.order_id.procurement_group_id != self.procurement_group_id
):
raise ValidationError(_("Procurement group must be equal to the order"))
@api.constrains("order_id", "company_id")
def check_order_company(self):
if self.order_id and self.order_id.company_id != self.company_id:
raise ValidationError(_("Company must be equal to the order"))
@api.constrains("order_id", "expected_date")
def check_order_expected_date(self):
if self.order_id and self.order_id.expected_date != self.expected_date:
raise ValidationError(_("Expected date must be equal to the order"))
@api.constrains("order_id", "picking_policy")
def check_order_picking_policy(self):
if self.order_id and self.order_id.picking_policy != self.picking_policy:
raise ValidationError(_("The picking policy must be equal to the order"))
def _action_confirm(self):
self._action_launch_procurement_rule()
self.write({"state": "open"})
def action_confirm(self):
self._action_confirm()
return True
def action_draft(self):
self.write({"state": "draft"})
return True
def action_cancel(self):
self.sudo().mapped("move_ids")._action_cancel()
self.write({"state": "cancel"})
return True
def action_done(self):
self.write({"state": "done"})
self.mapped("order_id").check_done()
return True
def check_done(self):
precision = self.env["decimal.precision"].precision_get(
"Product Unit of Measure"
)
for request in self:
allocated_qty = sum(request.allocation_ids.mapped("allocated_product_qty"))
qty_done = request.product_id.uom_id._compute_quantity(
allocated_qty, request.product_uom_id
)
if (
float_compare(
qty_done, request.product_uom_qty, precision_digits=precision
)
>= 0
):
request.action_done()
elif request._check_done_allocation():
request.action_done()
return True
def _check_done_allocation(self):
precision = self.env["decimal.precision"].precision_get(
"Product Unit of Measure"
)
self.ensure_one()
return (
self.allocation_ids
and float_compare(self.qty_cancelled, 0, precision_digits=precision) > 0
)
def _prepare_procurement_values(self, group_id=False):
"""Prepare specific key for moves or other components that
will be created from a procurement rule
coming from a stock request. This method could be override
in order to add other custom key that could be used in
move/po creation.
"""
return {
"date_planned": self.expected_date,
"warehouse_id": self.warehouse_id,
"stock_request_allocation_ids": self.id,
"group_id": group_id or self.procurement_group_id.id or False,
"route_ids": self.route_id,
"stock_request_id": self.id,
}
def _skip_procurement(self):
return self.state != "draft" or self.product_id.type not in ("consu", "product")
def _action_launch_procurement_rule(self):
"""
Launch procurement group run method with required/custom
fields genrated by a
stock request. procurement group will launch '_run_move',
'_run_buy' or '_run_manufacture'
depending on the stock request product rule.
"""
precision = self.env["decimal.precision"].precision_get(
"Product Unit of Measure"
)
errors = []
for request in self:
if request._skip_procurement():
continue
qty = 0.0
for move in request.move_ids.filtered(lambda r: r.state != "cancel"):
qty += move.product_qty
if float_compare(qty, request.product_qty, precision_digits=precision) >= 0:
continue
values = request._prepare_procurement_values(
group_id=request.procurement_group_id
)
try:
procurements = []
procurements.append(
self.env["procurement.group"].Procurement(
request.product_id,
request.product_uom_qty,
request.product_uom_id,
request.location_id,
request.name,
request.name,
self.env.company,
values,
)
)
self.env["procurement.group"].run(procurements)
except UserError as error:
errors.append(error.name)
if errors:
raise UserError("\n".join(errors))
return True
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.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")
return super().create(upd_vals)
def unlink(self):
if self.filtered(lambda r: r.state != "draft"):
raise UserError(_("Only requests on draft state can be unlinked"))
return super(StockRequest, self).unlink()

View File

@@ -0,0 +1,265 @@
# Copyright 2017-2020 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class StockRequest(models.AbstractModel):
_name = "stock.request.abstract"
_description = "Stock Request Template"
_inherit = ["mail.thread", "mail.activity.mixin"]
@api.model
def default_get(self, fields):
res = super(StockRequest, self).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
@api.depends(
"product_id",
"product_uom_id",
"product_uom_qty",
"product_id.product_tmpl_id.uom_id",
)
def _compute_product_qty(self):
for rec in self:
rec.product_qty = rec.product_uom_id._compute_quantity(
rec.product_uom_qty, rec.product_id.product_tmpl_id.uom_id
)
name = fields.Char("Name", copy=False, required=True, readonly=True, default="/")
warehouse_id = fields.Many2one(
"stock.warehouse", "Warehouse", ondelete="cascade", required=True
)
location_id = fields.Many2one(
"stock.location",
"Location",
domain=[("usage", "in", ["internal", "transit"])],
ondelete="cascade",
required=True,
)
product_id = fields.Many2one(
"product.product",
"Product",
domain=[("type", "in", ["product", "consu"])],
ondelete="cascade",
required=True,
)
allow_virtual_location = fields.Boolean(
related="company_id.stock_request_allow_virtual_loc", readonly=True
)
product_uom_id = fields.Many2one(
"uom.uom",
"Product Unit of Measure",
required=True,
default=lambda self: self._context.get("product_uom_id", False),
)
product_uom_qty = fields.Float(
"Quantity",
digits="Product Unit of Measure",
required=True,
help="Quantity, specified in the unit of measure indicated in the request.",
)
product_qty = fields.Float(
"Real Quantity",
compute="_compute_product_qty",
store=True,
copy=False,
digits="Product Unit of Measure",
help="Quantity in the default UoM of the product",
)
procurement_group_id = fields.Many2one(
"procurement.group",
"Procurement Group",
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, default=lambda self: self.env.company
)
route_id = fields.Many2one(
"stock.location.route",
string="Route",
domain="[('id', 'in', route_ids)]",
ondelete="restrict",
)
route_ids = fields.Many2many(
"stock.location.route",
string="Routes",
compute="_compute_route_ids",
readonly=True,
)
_sql_constraints = [
("name_uniq", "unique(name, company_id)", "Name must be unique")
]
@api.depends("product_id", "warehouse_id", "location_id")
def _compute_route_ids(self):
route_obj = self.env["stock.location.route"]
routes = route_obj.search(
[("warehouse_ids", "in", self.mapped("warehouse_id").ids)]
)
routes_by_warehouse = {}
for route in routes:
for warehouse in route.warehouse_ids:
routes_by_warehouse.setdefault(
warehouse.id, self.env["stock.location.route"]
)
routes_by_warehouse[warehouse.id] |= route
for record in self:
routes = route_obj
if record.product_id:
routes += record.product_id.mapped(
"route_ids"
) | record.product_id.mapped("categ_id").mapped("total_route_ids")
if record.warehouse_id and routes_by_warehouse.get(record.warehouse_id.id):
routes |= routes_by_warehouse[record.warehouse_id.id]
parents = record.get_parents().ids
record.route_ids = routes.filtered(
lambda r: any(p.location_id.id in parents for p in r.rule_ids)
)
def get_parents(self):
location = self.location_id
result = location
while location.location_id:
location = location.location_id
result |= location
return result
@api.constrains(
"company_id", "product_id", "warehouse_id", "location_id", "route_id"
)
def _check_company_constrains(self):
""" Check if the related models have the same company """
for rec in self:
if (
rec.product_id.company_id
and rec.product_id.company_id != rec.company_id
):
raise ValidationError(
_(
"You have entered a product that is assigned "
"to another company."
)
)
if (
rec.location_id.company_id
and rec.location_id.company_id != rec.company_id
):
raise ValidationError(
_(
"You have entered a location that is "
"assigned to another company."
)
)
if rec.warehouse_id.company_id != rec.company_id:
raise ValidationError(
_(
"You have entered a warehouse that is "
"assigned to another company."
)
)
if (
rec.route_id
and rec.route_id.company_id
and rec.route_id.company_id != rec.company_id
):
raise ValidationError(
_(
"You have entered a route that is "
"assigned to another company."
)
)
@api.constrains("product_id")
def _check_product_uom(self):
"""Check if the UoM has the same category as the
product standard UoM"""
if any(
request.product_id.uom_id.category_id != request.product_uom_id.category_id
for request in self
):
raise ValidationError(
_(
"You have to select a product unit of measure in the "
"same category than the default unit "
"of measure of the product"
)
)
@api.constrains("product_qty")
def _check_qty(self):
for rec in self:
if rec.product_qty <= 0:
raise ValidationError(
_("Stock Request product quantity has to be strictly positive.")
)
@api.onchange("warehouse_id")
def onchange_warehouse_id(self):
""" Finds location id for changed warehouse. """
res = {"domain": {}}
if self._name == "stock.request" and self.order_id:
# 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
# all request associated. Thus, no need to apply
# the onchange, as it could lead to inconsistencies.
return res
if self.warehouse_id:
loc_wh = self.location_id.get_warehouse()
if self.warehouse_id != loc_wh:
self.location_id = self.warehouse_id.lot_stock_id.id
if self.warehouse_id.company_id != self.company_id:
self.company_id = self.warehouse_id.company_id
return res
@api.onchange("location_id")
def onchange_location_id(self):
if self.location_id:
loc_wh = self.location_id.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()
@api.onchange("allow_virtual_location")
def onchange_allow_virtual_location(self):
if self.allow_virtual_location:
return {"domain": {"location_id": []}}
@api.onchange("company_id")
def onchange_company_id(self):
"""Sets a default warehouse when the company is changed and limits
the user selection of warehouses."""
if self.company_id and (
not self.warehouse_id or self.warehouse_id.company_id != self.company_id
):
self.warehouse_id = self.env["stock.warehouse"].search(
[("company_id", "=", self.company_id.id)], limit=1
)
self.onchange_warehouse_id()
return {"domain": {"warehouse_id": [("company_id", "=", self.company_id.id)]}}
@api.onchange("product_id")
def onchange_product_id(self):
res = {"domain": {}}
if self.product_id:
self.product_uom_id = self.product_id.uom_id.id
res["domain"]["product_uom_id"] = [
("category_id", "=", self.product_id.uom_id.category_id.id)
]
return res
res["domain"]["product_uom_id"] = []
return res

View File

@@ -0,0 +1,90 @@
# Copyright 2017-2020 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import api, fields, models
class StockRequestAllocation(models.Model):
_name = "stock.request.allocation"
_description = "Stock Request Allocation"
stock_request_id = fields.Many2one(
string="Stock Request",
comodel_name="stock.request",
required=True,
ondelete="cascade",
)
company_id = fields.Many2one(
string="Company",
comodel_name="res.company",
readonly=True,
related="stock_request_id.company_id",
store=True,
)
stock_move_id = fields.Many2one(
string="Stock Move",
comodel_name="stock.move",
required=True,
ondelete="cascade",
)
product_id = fields.Many2one(
string="Product",
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 Quantity (UoM)",
help="Quantity of the stock request allocated to the stock move, "
"in the UoM of the Stock Request",
)
requested_product_qty = fields.Float(
"Requested Quantity",
help="Quantity of the stock request allocated to the stock move, "
"in the default UoM of the product",
compute="_compute_requested_product_qty",
)
allocated_product_qty = fields.Float(
"Allocated Quantity",
copy=False,
help="Quantity of the stock request allocated to the stock move, "
"in the default UoM of the product",
)
open_product_qty = fields.Float(
"Open Quantity",
compute="_compute_open_product_qty",
)
@api.depends(
"stock_request_id.product_id",
"stock_request_id.product_uom_id",
"requested_product_uom_qty",
)
def _compute_requested_product_qty(self):
for rec in self:
rec.requested_product_qty = rec.product_uom_id._compute_quantity(
rec.requested_product_uom_qty, rec.product_id.uom_id
)
@api.depends(
"requested_product_qty",
"allocated_product_qty",
"stock_move_id",
"stock_move_id.state",
)
def _compute_open_product_qty(self):
for rec in self:
if rec.stock_move_id.state in ["cancel", "done"]:
rec.open_product_qty = 0.0
else:
rec.open_product_qty = (
rec.requested_product_qty - rec.allocated_product_qty
)
if rec.open_product_qty < 0.0:
rec.open_product_qty = 0.0

View File

@@ -0,0 +1,361 @@
# 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 AccessError, UserError, ValidationError
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_request_order_states(self):
return self.env["stock.request"]._get_request_states()
def _get_request_order_states(self):
return self.__get_request_order_states()
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=_get_request_order_states,
string="Status",
copy=False,
default="draft",
index=True,
readonly=True,
tracking=True,
)
requested_by = fields.Many2one(
"res.users",
"Requested by",
required=True,
tracking=True,
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.company,
)
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", copy=True
)
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.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.get_warehouse()
if self.warehouse_id != loc_wh:
self.location_id = self.warehouse_id.lot_stock_id
self.with_context(no_change_childs=True).onchange_location_id()
if self.warehouse_id.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.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
def action_confirm(self):
self.mapped("stock_request_ids").action_confirm()
self.write({"state": "open"})
return True
def action_draft(self):
self.mapped("stock_request_ids").action_draft()
self.write({"state": "draft"})
return True
def action_cancel(self):
self.mapped("stock_request_ids").action_cancel()
self.write({"state": "cancel"})
return True
def action_done(self):
self.write({"state": "done"})
return True
def check_done(self):
for rec in self:
if not rec.stock_request_ids.filtered(lambda r: r.state != "done"):
rec.action_done()
return
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
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)
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

View File

@@ -0,0 +1,42 @@
# Copyright 2017-2020 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from odoo import models
class StockRule(models.Model):
_inherit = "stock.rule"
def _get_stock_move_values(
self,
product_id,
product_qty,
product_uom,
location_id,
name,
origin,
company_id,
values,
):
result = super(StockRule, self)._get_stock_move_values(
product_id,
product_qty,
product_uom,
location_id,
name,
origin,
company_id,
values,
)
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

View File

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

View File

@@ -0,0 +1,17 @@
To configure this module:
* Go to Stock Requests > Settings
Users should be assigned to the groups 'Stock Request / User' or 'Stock
Request / Manager'.
## Group Stock Request / User
* Can see her/his own Stock Requests, and others that she/he's been granted
permission to follow.
* Can create/update only her/his Stock Requests.
## Group Stock Request / Manager
* Can fully manage all Stock Requests

View File

@@ -0,0 +1,13 @@
* Jordi Ballester (EFICENT) <jordi.ballester@forgeflow.com>.
* Enric Tobella <etobella@creublanca.es>
* Atte Isopuro <atte.isopuro@avoin.systems>
* Lois Rilo <lois.rilo@forgeflow.com>
* Raul Martin <raul.martin@braintec-group.com>
* Serpent Consulting Services Pvt. Ltd. <support@serpentcs.com>
* `Open Source Integrators <https://www.opensourceintegrators.com>`_
* Maxime Chambreuil <mchambreuil@opensourceintegrators.com>
* Steve Campbell <scampbell@opensourceintegrators.com>
* Héctor Villarreal <hector.villarreal@forgeflow.com>
* Kitti U. <kittiu@ecosoft.co.th>

View File

@@ -0,0 +1,2 @@
This module was written to allow users to request products that are
frequently stocked by the company, to be transferred to their chosen location.

View File

@@ -0,0 +1,5 @@
There is no way to achieve Storck Request and Stock Request Orders.
It should be developed taking into account that only Cancel and Done
stock request can be archived.
It is also required to manage active field logically from Orders to SRs.

View File

@@ -0,0 +1,16 @@
## Creation
* Go to 'Stock Requests / Stock Requests' and create a new Request.
* Indicate a product, quantity and location.
* Press 'Confirm'.
Upon confirmation the request will be evaluated using the procurement rules
for the selected location.
In case that transfers are created, the user will be able to access to them
from the button 'Transfers' available in the Stock Request.
## Cancel
When the user cancels a Stock Request, the related pending stock moves will be
also cancelled.

View File

@@ -0,0 +1,17 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_stock_request_user,stock request user,model_stock_request,group_stock_request_user,1,1,1,
access_stock_request_manager,stock request manager,model_stock_request,group_stock_request_manager,1,1,1,1
access_stock_request_stock_user,stock.request stock user,model_stock_request,stock.group_stock_user,1,0,0,0
access_stock_request_allocation_user,stock request allocation user,model_stock_request_allocation,group_stock_request_user,1,1,1,1
access_stock_request_allocation_manager,stock request allocation manager,model_stock_request_allocation,group_stock_request_manager,1,1,1,1
access_stock_request_allocation_stock_user,stock.request.allocation stock user,model_stock_request_allocation,base.group_user,1,0,0,0
access_stock_location_user,stock.location.user,stock.model_stock_location,group_stock_request_user,1,0,0,0
access_stock_location_request_manager,stock.location request manager,stock.model_stock_location,group_stock_request_manager,1,0,0,0
access_stock_rule_request_manager,stock_rule_request_manager,stock.model_stock_rule,group_stock_request_manager,1,0,0,0
access_stock_rule_user,stock_rule_user,stock.model_stock_rule,group_stock_request_user,1,0,0,0
access_stock_request_order_user,stock request user,model_stock_request_order,group_stock_request_user,1,1,1,0
access_stock_request_order_manager,stock request manager,model_stock_request_order,group_stock_request_manager,1,1,1,1
access_stock_request_user_warehouse,stock request user Warehouse,stock.model_stock_warehouse,group_stock_request_user,1,0,0,0
access_stock_request_manager_warehouse,stock request manager Warehouse,stock.model_stock_warehouse,group_stock_request_manager,1,0,0,0
access_stock_request_user_stock_move,stock request user stock move,stock.model_stock_move,group_stock_request_user,1,0,0,0
access_stock_request_manager_stock_move,stock request manager stock move,stock.model_stock_move,group_stock_request_manager,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_stock_request_user stock request user model_stock_request group_stock_request_user 1 1 1
3 access_stock_request_manager stock request manager model_stock_request group_stock_request_manager 1 1 1 1
4 access_stock_request_stock_user stock.request stock user model_stock_request stock.group_stock_user 1 0 0 0
5 access_stock_request_allocation_user stock request allocation user model_stock_request_allocation group_stock_request_user 1 1 1 1
6 access_stock_request_allocation_manager stock request allocation manager model_stock_request_allocation group_stock_request_manager 1 1 1 1
7 access_stock_request_allocation_stock_user stock.request.allocation stock user model_stock_request_allocation base.group_user 1 0 0 0
8 access_stock_location_user stock.location.user stock.model_stock_location group_stock_request_user 1 0 0 0
9 access_stock_location_request_manager stock.location request manager stock.model_stock_location group_stock_request_manager 1 0 0 0
10 access_stock_rule_request_manager stock_rule_request_manager stock.model_stock_rule group_stock_request_manager 1 0 0 0
11 access_stock_rule_user stock_rule_user stock.model_stock_rule group_stock_request_user 1 0 0 0
12 access_stock_request_order_user stock request user model_stock_request_order group_stock_request_user 1 1 1 0
13 access_stock_request_order_manager stock request manager model_stock_request_order group_stock_request_manager 1 1 1 1
14 access_stock_request_user_warehouse stock request user Warehouse stock.model_stock_warehouse group_stock_request_user 1 0 0 0
15 access_stock_request_manager_warehouse stock request manager Warehouse stock.model_stock_warehouse group_stock_request_manager 1 0 0 0
16 access_stock_request_user_stock_move stock request user stock move stock.model_stock_move group_stock_request_user 1 0 0 0
17 access_stock_request_manager_stock_move stock request manager stock move stock.model_stock_move group_stock_request_manager 1 0 0 0

View File

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record model="ir.module.category" id="module_category_stock_request">
<field name="name">Stock Request</field>
<field name="parent_id" ref="base.module_category_inventory_inventory" />
<field name="sequence">10</field>
</record>
<record id="group_stock_request_user" model="res.groups">
<field name="name">Stock Request User</field>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]" />
<field name="category_id" ref="module_category_stock_request" />
</record>
<record id="group_stock_request_manager" model="res.groups">
<field name="name">Stock Request Manager</field>
<field
name="users"
eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"
/>
<field
name="implied_ids"
eval="[(4, ref('stock_request.group_stock_request_user')),(4, ref('stock.group_stock_user'))]"
/>
<field name="category_id" ref="module_category_stock_request" />
</record>
<record id="group_stock_request_order" model="res.groups">
<field name="name">Stock Request Order</field>
<field name="category_id" ref="base.module_category_hidden" />
</record>
<record id="stock.group_stock_user" model="res.groups">
<field name="implied_ids" eval="[(4, ref('group_stock_request_user'))]" />
</record>
<data noupdate="1">
<record model="ir.rule" id="stock_picking_rule">
<field name="name">stock_request multi-company</field>
<field
name="model_id"
search="[('model','=','stock.request')]"
model="ir.model"
/>
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record id="stock_request_followers_rule" model="ir.rule">
<field name="name">Follow Stock Request</field>
<field name="model_id" ref="model_stock_request" />
<field name="groups" eval="[(6,0, [ref('group_stock_request_user')])]" />
<field name="perm_read" eval="True" />
<field name="perm_write" eval="False" />
<field name="perm_create" eval="False" />
<field name="perm_unlink" eval="False" />
<field name="domain_force">['|',('requested_by','=',user.id),
('message_partner_ids', 'in', [user.partner_id.id])]</field>
</record>
<record id="stock_request_rule" model="ir.rule">
<field name="name">Stock Request User</field>
<field name="model_id" ref="model_stock_request" />
<field name="groups" eval="[(6,0, [ref('group_stock_request_user')])]" />
<field name="perm_read" eval="True" />
<field name="perm_write" eval="True" />
<field name="perm_create" eval="True" />
<field name="perm_unlink" eval="True" />
<field name="domain_force">[('requested_by','=',user.id)]</field>
</record>
<record id="stock_request_manager_rule" model="ir.rule">
<field name="name">Stock Request Manager</field>
<field name="model_id" ref="model_stock_request" />
<field name="groups" eval="[(6,0, [ref('group_stock_request_manager')])]" />
<field name="perm_read" eval="True" />
<field name="perm_write" eval="True" />
<field name="perm_create" eval="True" />
<field name="perm_unlink" eval="True" />
</record>
<record model="ir.rule" id="stock_request_order_picking_rule">
<field name="name">stock_request_order multi-company</field>
<field name="model_id" ref="model_stock_request_order" />
<field name="global" eval="True" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record id="stock_request_order_followers_rule" model="ir.rule">
<field name="name">Follow Stock Request Order</field>
<field name="model_id" ref="model_stock_request_order" />
<field
name="groups"
eval="[(6,0, [ref('stock_request.group_stock_request_user')])]"
/>
<field name="perm_read" eval="True" />
<field name="perm_write" eval="False" />
<field name="perm_create" eval="False" />
<field name="perm_unlink" eval="False" />
<field name="domain_force">['|',('requested_by','=',user.id),
('message_partner_ids', 'in', [user.partner_id.id])]</field>
</record>
<record id="stock_request_order_rule" model="ir.rule">
<field name="name">Stock Request Order User</field>
<field name="model_id" ref="model_stock_request_order" />
<field
name="groups"
eval="[(6,0, [ref('stock_request.group_stock_request_user')])]"
/>
<field name="perm_read" eval="True" />
<field name="perm_write" eval="True" />
<field name="perm_create" eval="True" />
<field name="perm_unlink" eval="True" />
<field name="domain_force">[('requested_by','=',user.id)]</field>
</record>
<record id="stock_request_order_manager_rule" model="ir.rule">
<field name="name">Stock Request Manager</field>
<field name="model_id" ref="model_stock_request_order" />
<field
name="groups"
eval="[(6,0, [ref('stock_request.group_stock_request_manager')])]"
/>
<field name="perm_read" eval="True" />
<field name="perm_write" eval="True" />
<field name="perm_create" eval="True" />
<field name="perm_unlink" eval="True" />
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg8"
sodipodi:docname="icon.svg"
inkscape:version="0.92.4 (f8dce91, 2019-08-02)">
<defs
id="defs2">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4562">
<rect
ry="4.1577382"
y="13.040181"
x="-124.92113"
height="98.866074"
width="99.811012"
id="rect4564"
style="fill:#aa0000;stroke-width:0.26458332"
clip-path="none" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4578">
<rect
ry="4.1577382"
y="1.5119057"
x="-120.00744"
height="97.930626"
width="99.811012"
id="rect4580"
style="fill:#aa0000;stroke-width:0.26458332" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.4"
inkscape:cx="35.432324"
inkscape:cy="70.109427"
inkscape:document-units="mm"
inkscape:current-layer="layer2"
showgrid="false"
inkscape:snap-page="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1052"
inkscape:window-x="0"
inkscape:window-y="28"
inkscape:window-maximized="1"
inkscape:snap-global="false">
<sodipodi:guide
position="27.285156,77.130766"
orientation="-0.70710678,0.70710678"
id="guide4619"
inkscape:locked="false" />
<sodipodi:guide
position="65.956845,10.583333"
orientation="-0.70710678,0.70710678"
id="guide4621"
inkscape:locked="false" />
<sodipodi:guide
position="67.704985,78.09933"
orientation="-0.70710678,0.70710678"
id="guide4623"
inkscape:locked="false" />
<sodipodi:guide
position="62.407434,24.722431"
orientation="-0.70710678,0.70710678"
id="guide4625"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-197)"
style="display:inline" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2"
style="display:inline">
<rect
y="0"
x="0"
height="25.164532"
width="100"
id="rect4535"
style="fill:#de8787;stroke-width:0.13272627"
ry="4.9136906"
inkscape:export-xdpi="91.327972"
inkscape:export-ydpi="91.327972" />
<rect
ry="4.9136906"
style="fill:#501616;stroke-width:0.13272627"
id="rect4566"
width="100"
height="25.164532"
x="0.18899068"
y="74.958138"
inkscape:export-xdpi="91.327972"
inkscape:export-ydpi="91.327972" />
<rect
style="fill:#aa0000;stroke-width:0.26458332"
id="rect914"
width="99.811012"
height="97.930626"
x="0.18898809"
y="1.1339295"
ry="4.1577382"
inkscape:export-xdpi="91.327972"
inkscape:export-ydpi="91.327972" />
<path
id="path61"
style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 97.234379,149.70387 0.28348,94.58853 c 0,0 0.75595,3.1183 3.496281,3.87426 h 28.91518 c 0,0 3.30729,-0.94494 3.87426,-3.59077 v -7.74852 c 0,0 -0.94494,-3.1183 -3.68527,-3.59077 -2.74033,-0.47247 -18.23735,0 -18.23735,0 v -82.96577 c 0,0 -0.47247,-3.68526 -3.40179,-3.77976 h -7.46503 c 0,0 -2.834821,-0.0945 -3.779761,3.2128 z m -40.72672,-3.30742 -32.22284,0.0946 c 0,0 -11.24479,1.32285 -11.24479,11.15022 l 0.0946,25.22999 -10.866522,0.18914 c 0,0 -9.35491,-0.8503 -11.24479,10.77247 l 0.47232,47.05759 c 0,0 1.79532,21.82849 21.733562,21.63951 19.93824,-0.18899 21.73356,-21.54494 21.73356,-21.54494 l 14.45751,-0.0946 c 0,0 1.22835,21.734 21.73356,21.63951 0,0 21.1671,-0.28386 21.73407,-21.63951 0,0 -0.28348,-10.01622 -7.27604,-16.34732 v -24.28483 c 0,0 0.28326,-3.21309 -0.94516,-5.66993 -1.22842,-2.45685 -17.85938,-41.76593 -17.85938,-41.76593 0,0 -1.88966,-5.19754 -10.29963,-6.42596 z m -28.86542,14.55415 26.45989,0.13384 17.10489,39.28856 -0.13332,3.87521 h -21.91598 l -21.38164,-21.38112 z m -14.52107,69.22316 a 10.657372,10.657372 0 0 1 10.65722,10.65723 10.657372,10.657372 0 0 1 -10.65722,10.65723 10.657372,10.657372 0 0 1 -10.657752,-10.65723 10.657372,10.657372 0 0 1 10.657752,-10.65723 z m 58.20833,0 a 10.657372,10.657372 0 0 1 10.65723,10.65723 10.657372,10.657372 0 0 1 -10.65723,10.65723 10.657372,10.657372 0 0 1 -10.65775,-10.65723 10.657372,10.657372 0 0 1 10.65775,-10.65723 z"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
d="m -92.911273,23.247206 -27.285157,27.28811 v 49.842664 h 37.697532 17.674503 l 29.218074,-29.216604 -13.586893,-4.10161 -3.29823,-44.78112 -13.501505,13.50151 -6.362234,-9.16857 -20.55609,-3.36438"
style="display:inline;opacity:0.476;fill:#550000;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4576"
clip-path="url(#clipPath4578)"
transform="translate(120.21264,-0.31639914)"
inkscape:export-xdpi="91.327972"
inkscape:export-ydpi="91.327972" />
<path
inkscape:connector-curvature="0"
d="m 65.712353,24.643945 0.14027,46.803935 c 0,0 0.374059,1.542981 1.730014,1.917051 h 14.307701 c 0,0 1.636494,-0.467579 1.917043,-1.776775 v -3.834094 c 0,0 -0.467568,-1.542985 -1.823534,-1.77677 -1.355955,-0.233787 -9.024128,0 -9.024128,0 V 24.924486 c 0,0 -0.23379,-1.823526 -1.683265,-1.870286 h -3.693817 c 0,0 -1.402715,-0.04676 -1.870284,1.589745 z M 45.560112,23.00738 29.615728,23.05418 c 0,0 -5.564103,0.654568 -5.564103,5.517309 l 0.04681,12.484207 -5.376931,0.09359 c 0,0 -4.628961,-0.420742 -5.564104,5.330392 l 0.233712,23.284857 c 0,0 0.888353,10.801089 10.754117,10.707579 9.865764,-0.09354 10.754117,-10.660784 10.754117,-10.660784 l 7.153819,-0.04682 c 0,0 0.607805,10.754338 10.75411,10.707578 0,0 10.473823,-0.140456 10.754372,-10.707578 0,0 -0.140269,-4.956187 -3.600297,-8.088919 V 49.659084 c 0,0 0.14016,-1.589888 -0.467689,-2.805573 -0.607837,-1.215689 -8.83711,-20.666457 -8.83711,-20.666457 0,0 -0.935026,-2.571826 -5.096422,-3.179669 z m -14.283078,7.201629 13.092779,0.06622 8.463775,19.440614 -0.06602,1.917516 H 41.923187 L 31.343205,41.053641 Z m -7.18526,34.252739 a 5.2734399,5.2734399 0 0 1 5.273364,5.27337 5.2734399,5.2734399 0 0 1 -5.273364,5.273369 5.2734399,5.2734399 0 0 1 -5.273628,-5.273369 5.2734399,5.2734399 0 0 1 5.273628,-5.27337 z m 28.802424,0 a 5.2734399,5.2734399 0 0 1 5.273363,5.27337 5.2734399,5.2734399 0 0 1 -5.273363,5.273369 5.2734399,5.2734399 0 0 1 -5.273626,-5.273369 5.2734399,5.2734399 0 0 1 5.273626,-5.27337 z"
style="fill:#501616;stroke:none;stroke-width:0.1309201px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path4582"
inkscape:export-xdpi="91.327972"
inkscape:export-ydpi="91.327972" />
<path
id="path61-3"
style="fill:#ffffff;stroke:#ffffff;stroke-width:0.1309201px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 67.229989,22.903912 0.14027,46.803935 c 0,0 0.374059,1.542981 1.730014,1.917051 h 14.307701 c 0,0 1.636494,-0.467579 1.917043,-1.776775 v -3.834094 c 0,0 -0.467568,-1.542985 -1.823534,-1.77677 -1.355955,-0.233787 -9.024128,0 -9.024128,0 V 23.184453 c 0,0 -0.23379,-1.823526 -1.683265,-1.870286 h -3.693817 c 0,0 -1.402715,-0.04676 -1.870284,1.589745 z m -20.152241,-1.636565 -15.944384,0.0468 c 0,0 -5.564103,0.654568 -5.564103,5.517309 l 0.04681,12.484207 -5.376931,0.09359 c 0,0 -4.628961,-0.420742 -5.564104,5.330392 l 0.233712,23.284857 c 0,0 0.888353,10.801089 10.754117,10.707579 9.865764,-0.09354 10.754117,-10.660784 10.754117,-10.660784 l 7.153819,-0.04682 c 0,0 0.607805,10.754338 10.75411,10.707578 0,0 10.473823,-0.140456 10.754372,-10.707578 0,0 -0.140269,-4.956187 -3.600297,-8.088919 V 47.919051 c 0,0 0.14016,-1.589888 -0.467689,-2.805573 -0.607837,-1.215689 -8.83711,-20.666457 -8.83711,-20.666457 0,0 -0.935026,-2.571826 -5.096422,-3.179669 z m -14.283078,7.201629 13.092779,0.06622 8.463775,19.440614 -0.06602,1.917516 H 43.440823 L 32.860841,39.313608 Z m -7.18526,34.252739 a 5.2734399,5.2734399 0 0 1 5.273364,5.27337 5.2734399,5.2734399 0 0 1 -5.273364,5.273369 5.2734399,5.2734399 0 0 1 -5.273628,-5.273369 5.2734399,5.2734399 0 0 1 5.273628,-5.27337 z m 28.802424,0 a 5.2734399,5.2734399 0 0 1 5.273363,5.27337 5.2734399,5.2734399 0 0 1 -5.273363,5.273369 5.2734399,5.2734399 0 0 1 -5.273626,-5.273369 5.2734399,5.2734399 0 0 1 5.273626,-5.27337 z"
inkscape:connector-curvature="0"
inkscape:export-xdpi="91.327972"
inkscape:export-ydpi="91.327972" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -0,0 +1,476 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
<title>Stock Request</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="stock-request">
<h1 class="title">Stock Request</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/13.0/stock_request"><img alt="OCA/stock-logistics-warehouse" src="https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/stock-logistics-warehouse-13-0/stock-logistics-warehouse-13-0-stock_request"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/153/13.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module was written to allow users to request products that are
frequently stocked by the company, to be transferred to their chosen location.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id3">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id1">Configuration</a></h1>
<p>To configure this module:</p>
<ul class="simple">
<li>Go to Stock Requests &gt; Settings</li>
</ul>
<p>Users should be assigned to the groups Stock Request / User or Stock
Request / Manager.</p>
<p>## Group Stock Request / User</p>
<ul class="simple">
<li>Can see her/his own Stock Requests, and others that she/hes been granted
permission to follow.</li>
<li>Can create/update only her/his Stock Requests.</li>
</ul>
<p>## Group Stock Request / Manager</p>
<ul class="simple">
<li>Can fully manage all Stock Requests</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<p>## Creation</p>
<ul class="simple">
<li>Go to Stock Requests / Stock Requests and create a new Request.</li>
<li>Indicate a product, quantity and location.</li>
<li>Press Confirm.</li>
</ul>
<p>Upon confirmation the request will be evaluated using the procurement rules
for the selected location.</p>
<p>In case that transfers are created, the user will be able to access to them
from the button Transfers available in the Stock Request.</p>
<p>## Cancel</p>
<p>When the user cancels a Stock Request, the related pending stock moves will be
also cancelled.</p>
</div>
<div class="section" id="known-issues-roadmap">
<h1><a class="toc-backref" href="#id3">Known issues / Roadmap</a></h1>
<p>There is no way to achieve Storck Request and Stock Request Orders.
It should be developed taking into account that only Cancel and Done
stock request can be archived.</p>
<p>It is also required to manage active field logically from Orders to SRs.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/issues/new?body=module:%20stock_request%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id6">Authors</a></h2>
<ul class="simple">
<li>ForgeFlow</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id7">Contributors</a></h2>
<ul class="simple">
<li>Jordi Ballester (EFICENT) &lt;<a class="reference external" href="mailto:jordi.ballester&#64;forgeflow.com">jordi.ballester&#64;forgeflow.com</a>&gt;.</li>
<li>Enric Tobella &lt;<a class="reference external" href="mailto:etobella&#64;creublanca.es">etobella&#64;creublanca.es</a>&gt;</li>
<li>Atte Isopuro &lt;<a class="reference external" href="mailto:atte.isopuro&#64;avoin.systems">atte.isopuro&#64;avoin.systems</a>&gt;</li>
<li>Lois Rilo &lt;<a class="reference external" href="mailto:lois.rilo&#64;forgeflow.com">lois.rilo&#64;forgeflow.com</a>&gt;</li>
<li>Raul Martin &lt;<a class="reference external" href="mailto:raul.martin&#64;braintec-group.com">raul.martin&#64;braintec-group.com</a>&gt;</li>
<li>Serpent Consulting Services Pvt. Ltd. &lt;<a class="reference external" href="mailto:support&#64;serpentcs.com">support&#64;serpentcs.com</a>&gt;</li>
<li><a class="reference external" href="https://www.opensourceintegrators.com">Open Source Integrators</a><ul>
<li>Maxime Chambreuil &lt;<a class="reference external" href="mailto:mchambreuil&#64;opensourceintegrators.com">mchambreuil&#64;opensourceintegrators.com</a>&gt;</li>
<li>Steve Campbell &lt;<a class="reference external" href="mailto:scampbell&#64;opensourceintegrators.com">scampbell&#64;opensourceintegrators.com</a>&gt;</li>
</ul>
</li>
<li>Héctor Villarreal &lt;<a class="reference external" href="mailto:hector.villarreal&#64;forgeflow.com">hector.villarreal&#64;forgeflow.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id8">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/stock-logistics-warehouse/tree/13.0/stock_request">OCA/stock-logistics-warehouse</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="action_variant_generate_stock_request_orders" model="ir.actions.server">
<field name="name">Request Stock</field>
<field name="type">ir.actions.server</field>
<field name="state">code</field>
<field name="model_id" ref="product.model_product_product" />
<field name="binding_model_id" ref="product.model_product_product" />
<field name="code">
action = records.env['stock.request.order']._create_from_product_multiselect(records)
</field>
</record>
<record
id="action_template_generate_stock_request_orders"
model="ir.actions.server"
>
<field name="name">Request Stock</field>
<field name="type">ir.actions.server</field>
<field name="state">code</field>
<field name="model_id" ref="product.model_product_template" />
<field name="binding_model_id" ref="product.model_product_template" />
<field name="code">
action = records.env['stock.request.order']._create_from_product_multiselect(records)
</field>
</record>
</odoo>

View File

@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright 2018 Creu Blanca
License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
-->
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.stock_request</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[hasclass('settings')]" position="inside">
<div
class="app_settings_block"
data-string="Stock Request"
data-key="stock_request"
groups="stock_request.group_stock_request_manager"
>
<h2>Orders &amp; Configuration</h2>
<div class="row mt16 o_settings_container" id="stock_request">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="group_stock_request_order" />
</div>
<div class="o_setting_right_pane">
<label
string="Enable Orders"
for="group_stock_request_order"
/>
<div class="text-muted">
Activates Stock Request Orders
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="stock_request_allow_virtual_loc" />
</div>
<div class="o_setting_right_pane">
<label
string="Allow All Locations Types"
for="stock_request_allow_virtual_loc"
/>
<div class="text-muted">
By default only internal and transit locations are allowed in Stock Request and Orders.
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_stock_request_submit" />
</div>
<div class="o_setting_right_pane">
<label
string="Enable Submitted State"
for="module_stock_request_purchase"
/>
<div class="text-muted">
Add State to Stock Request and Stock Request Orders if activated.
</div>
</div>
</div>
</div>
<h2>Purchases</h2>
<div
class="row mt16 o_settings_container"
id="stock_request_purchase"
>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_stock_request_purchase" />
</div>
<div class="o_setting_right_pane">
<label
string="Enable Stock Requests for Purchases"
for="module_stock_request_purchase"
/>
<div class="text-muted">
Use Purchases with Stock Requests
</div>
</div>
</div>
</div>
<h2>Manufacturing</h2>
<div class="row mt16 o_settings_container" id="stock_request_mrp">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_stock_request_mrp" />
</div>
<div class="o_setting_right_pane">
<label
string="Enable Stock Requests for Manufacturing"
for="module_stock_request_mrp"
/>
<div class="text-muted">
Use Manufacturing Orders with Stock Requests
</div>
</div>
</div>
</div>
<h2>Kanban</h2>
<div
class="row mt16 o_settings_container"
id="stock_request_purchase"
>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_stock_request_kanban" />
</div>
<div class="o_setting_right_pane">
<label
string="Enable Stock Requests Kanban cards"
for="module_stock_request_purchase"
/>
<div class="text-muted">
Use Kanban cards for consumable products
</div>
</div>
</div>
</div>
<h2>Analytic</h2>
<div
class="row mt16 o_settings_container"
id="stock_request_analytic"
>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="module_stock_request_analytic" />
</div>
<div class="o_setting_right_pane">
<label
string="Enable Analytic Accounting in Stock Requests"
for="module_stock_request_analytic"
/>
<div class="text-muted">
Analytic accounting in Stock Requests
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
<record id="action_stock_request_config" model="ir.actions.act_window">
<field name="name">Settings</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">inline</field>
<field name="context">{'module' : 'stock_request'}</field>
</record>
</odoo>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_stock_move_operations" model="ir.ui.view">
<field name="name">stock.move.operations.form</field>
<field name="model">stock.move</field>
<field name="inherit_id" ref="stock.view_stock_move_operations" />
<field name="arch" type="xml">
<field name="move_line_ids" position="after">
<newline />
<field name="allocation_ids" />
</field>
</field>
</record>
<record id="view_move_form" model="ir.ui.view">
<field name="name">stock.move.form</field>
<field name="model">stock.move</field>
<field name="inherit_id" ref="stock.view_move_form" />
<field name="arch" type="xml">
<group name="linked_group" position="after">
<newline />
<group name="allocations" string="Stock Request Allocations">
<field name="allocation_ids" />
</group>
</group>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_picking_form" model="ir.ui.view">
<field name="name">stock.picking.form</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form" />
<field eval="12" name="priority" />
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button
type="object"
name="action_view_stock_request"
class="oe_stat_button"
icon="fa-chain"
attrs="{'invisible':[('stock_request_ids', '=', [])]}"
>
<field
name="stock_request_count"
widget="statinfo"
string="Stock Requests"
/>
<field name="stock_request_ids" invisible="1" />
</button>
</div>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2017 ForgeFlow
License LGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_stock_request_allocation_tree" model="ir.ui.view">
<field name="name">stock.request.allocation.tree</field>
<field name="model">stock.request.allocation</field>
<field name="arch" type="xml">
<tree string="Stock Request Allocations">
<field name="stock_request_id" />
<field name="stock_move_id" />
<field name="product_id" />
<field name="requested_product_uom_qty" />
<field
name="product_uom_id"
options="{'no_open': True, 'no_create': True}"
groups="uom.group_uom"
/>
<field name="requested_product_qty" />
<field name="allocated_product_qty" />
<field name="open_product_qty" />
<field name="company_id" groups="base.group_multi_company" />
</tree>
</field>
</record>
<record id="view_stock_request_allocation_form" model="ir.ui.view">
<field name="name">stock.request.allocation.form</field>
<field name="model">stock.request.allocation</field>
<field name="arch" type="xml">
<form string="Stock Request Allocations">
<sheet>
<group>
<group>
<field name="stock_request_id" />
<field name="stock_move_id" />
</group>
<group>
<field name="product_id" />
<field name="requested_product_uom_qty" />
<field
name="product_uom_id"
options="{'no_open': True, 'no_create': True}"
groups="uom.group_uom"
/>
<field name="requested_product_qty" />
<field name="allocated_product_qty" />
<field name="open_product_qty" />
<field
name="company_id"
groups="base.group_multi_company"
options="{'no_create': True}"
/>
</group>
</group>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<menuitem
id="menu_stock_request_root"
name="Stock Requests"
groups="stock_request.group_stock_request_user,stock_request.group_stock_request_manager"
web_icon="stock_request,static/description/icon.png"
sequence="100"
/>
<menuitem
id="menu_stock_request_operations"
parent="menu_stock_request_root"
name="Operations"
sequence="10"
/>
<menuitem
id="stock_request_order_menu"
name="Stock Request Orders"
parent="menu_stock_request_operations"
action="stock_request_order_action"
groups="group_stock_request_order"
sequence="20"
/>
<menuitem
id="menu_stock_request"
action="action_stock_request_form"
name="Stock Requests"
parent="menu_stock_request_operations"
sequence="30"
/>
<menuitem
id="menu_stock_request_master_data"
parent="menu_stock_request_root"
name="Master Data"
sequence="100"
/>
<menuitem
id="menu_stock_request_master_data"
parent="menu_stock_request_root"
name="Master Data"
sequence="100"
/>
<menuitem
id="menu_stock_request_config"
name="Settings"
parent="menu_stock_request_root"
sequence="999"
action="action_stock_request_config"
groups="base.group_system"
/>
</odoo>

View File

@@ -0,0 +1,204 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record model="ir.ui.view" id="stock_request_order_tree">
<field name="name">stock.request.order.tree</field>
<field name="model">stock.request.order</field>
<field name="arch" type="xml">
<tree string="Stock requests">
<field name="name" />
<field name="warehouse_id" groups="stock.group_stock_multi_locations" />
<field name="location_id" groups="stock.group_stock_multi_locations" />
<field name="state" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="stock_request_order_form">
<field name="name">stock.request.order.form</field>
<field name="model">stock.request.order</field>
<field name="arch" type="xml">
<form string="Stock request">
<header>
<button
name="action_confirm"
string="Confirm"
type="object"
attrs="{'invisible': [('state', 'not in', ['draft'])]}"
/>
<button
name="action_cancel"
states="draft,open"
type="object"
string="Cancel"
/>
<button
name="action_draft"
states="cancel"
type="object"
string="Set to Draft"
/>
<field name="state" widget="statusbar" />
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<field name="picking_ids" invisible="1" />
<button
type="object"
name="action_view_transfer"
class="oe_stat_button"
icon="fa-truck"
attrs="{'invisible': [('picking_count', '=', 0)]}"
groups="stock.group_stock_user"
>
<field
name="picking_count"
widget="statinfo"
string="Transfers"
/>
</button>
<button
type="object"
name="action_view_stock_requests"
class="oe_stat_button"
icon="fa-chain"
attrs="{'invisible': [('state', '=', 'draft')]}"
groups="stock.group_stock_user"
>
<field
name="stock_request_count"
widget="statinfo"
string="Stock Requests"
/>
</button>
</div>
<div class="oe_title">
<label for="name" string="Stock Request" />
<h1>
<field name="name" readonly="1" />
</h1>
</div>
<group>
<group>
<field name="expected_date" />
<field name="picking_policy" />
</group>
<group>
<field
name="warehouse_id"
widget="selection"
groups="stock.group_stock_multi_locations"
/>
<field
name="location_id"
groups="stock.group_stock_multi_locations"
/>
<field name="allow_virtual_location" invisible="1" />
<field
name="procurement_group_id"
groups="stock.group_adv_location"
/>
<field
name="company_id"
groups="base.group_multi_company"
options="{'no_create': True}"
/>
</group>
</group>
<notebook>
<page name="items" string="Items">
<field
name="stock_request_ids"
context="{
'default_expected_date':expected_date,
'default_picking_policy': picking_policy,
'default_warehouse_id': warehouse_id,
'default_location_id': location_id,
'default_procurement_group_id': procurement_group_id,
'default_company_id': company_id,
'default_state': state,
}"
attrs="{'readonly': [('state', '!=', 'draft')]}"
>
<tree editable="bottom">
<field name="name" readonly="1" />
<field name="product_id" />
<field
name="product_uom_id"
options="{'no_open': True, 'no_create': True}"
groups="uom.group_uom"
/>
<field
name="route_id"
options="{'no_create': True}"
groups="stock.group_stock_multi_locations"
/>
<field name="route_ids" invisible="1" />
<field name="product_uom_qty" />
<field name="qty_in_progress" />
<field name="qty_done" />
<field name="expected_date" invisible="1" />
<field name="picking_policy" invisible="1" />
<field name="warehouse_id" invisible="1" />
<field name="location_id" invisible="1" />
<field name="procurement_group_id" invisible="1" />
<field name="company_id" invisible="1" />
<field name="state" />
</tree>
</field>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" />
<field name="activity_ids" widget="mail_activity" />
<field name="message_ids" widget="mail_thread" />
</div>
</form>
</field>
</record>
<record model="ir.ui.view" id="stock_request_order_search">
<field name="name">stock.request.order.search</field>
<field name="model">stock.request.order</field>
<field name="arch" type="xml">
<search string="Stock Requests Order Search">
<field name="name" string="Stock Request Orders" />
<field name="state" />
<field name="warehouse_id" />
<field name="location_id" groups="stock.group_stock_multi_locations" />
<field name="company_id" groups="base.group_multi_company" />
<separator />
<filter string="Draft" name="draft" domain="[('state','=','draft')]" />
<filter
string="In Progress"
name="open"
domain="[('state','=','open')]"
/>
<filter string="Finished" name="done" domain="[('state','=','done')]" />
<filter
string="Cancelled"
name="cancel"
domain="[('state','=','cancel')]"
/>
<group expand="0" string="Group By">
<filter
string="Warehouse"
name="warehouse"
domain="[]"
context="{'group_by':'warehouse_id'}"
/>
<filter
string="Location"
name="location"
domain="[]"
context="{'group_by':'location_id'}"
/>
</group>
</search>
</field>
</record>
<record model="ir.actions.act_window" id="stock_request_order_action">
<field name="name">Stock Request Orders</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">stock.request.order</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>

View File

@@ -0,0 +1,251 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2017 ForgeFlow
License LGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_stock_request_tree" model="ir.ui.view">
<field name="name">stock.request.tree</field>
<field name="model">stock.request</field>
<field name="arch" type="xml">
<tree
string="Stock Requests"
decoration-muted="state == 'cancel'"
decoration-bf="message_needaction==True"
>
<field name="message_needaction" invisible="1" />
<field name="name" />
<field name="warehouse_id" groups="stock.group_stock_multi_locations" />
<field name="location_id" groups="stock.group_stock_multi_locations" />
<field name="allow_virtual_location" invisible="1" />
<field
name="route_id"
options="{'no_create': True}"
groups="stock.group_stock_multi_locations"
/>
<field name="product_id" />
<field
name="product_uom_id"
options="{'no_open': True, 'no_create': True}"
groups="uom.group_uom"
/>
<field name="product_uom_qty" />
<field name="qty_in_progress" />
<field name="qty_done" />
<field name="qty_cancelled" />
<field name="state" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="stock_request_search">
<field name="name">stock.request.search</field>
<field name="model">stock.request</field>
<field name="arch" type="xml">
<search string="Stock Requests Search">
<field name="name" string="Stock Requests" />
<field name="warehouse_id" />
<field name="location_id" groups="stock.group_stock_multi_locations" />
<field name="company_id" groups="base.group_multi_company" />
<field name="product_id" />
<separator />
<filter
name="current_requests"
string="Current requests"
domain="['|', ('expected_date', '>', (datetime.date.today() - relativedelta(months=1)).strftime('%Y-%m-01')), ('state', '!=', 'done')]"
/>
<separator />
<filter string="Draft" name="draft" domain="[('state','=','draft')]" />
<filter
string="In Progress"
name="open"
domain="[('state','=','open')]"
/>
<filter string="Finished" name="done" domain="[('state','=','done')]" />
<filter
string="Cancelled"
name="cancel"
domain="[('state','=','cancel')]"
/>
<group expand="0" string="Group By">
<filter
name="warehouse"
string="Warehouse"
domain="[]"
context="{'group_by':'warehouse_id'}"
/>
<filter
name="location"
string="Location"
domain="[]"
context="{'group_by':'location_id'}"
/>
<filter
name="status"
string="Status"
domain="[]"
context="{'group_by':'state'}"
/>
<filter
name="route"
string="Route"
domain="[]"
context="{'group_by':'route_id'}"
/>
<filter
name="product"
string="Product"
domain="[]"
context="{'group_by':'product_id'}"
/>
</group>
</search>
</field>
</record>
<record id="view_stock_request_form" model="ir.ui.view">
<field name="name">stock.request.form</field>
<field name="model">stock.request</field>
<field name="arch" type="xml">
<form string="Stock Requests">
<header>
<button
name="action_confirm"
string="Confirm"
type="object"
attrs="{'invisible': [('state', 'not in', ['draft'])]}"
/>
<button
name="action_cancel"
states="draft,open"
type="object"
string="Cancel"
/>
<button
name="action_draft"
states="cancel"
type="object"
string="Set to Draft"
/>
<button
name="action_done"
string="Done"
type="object"
attrs="{'invisible': [('state', 'not in', ['open'])]}"
/>
<field name="state" widget="statusbar" />
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<field name="picking_ids" invisible="1" />
<button
type="object"
name="action_view_transfer"
class="oe_stat_button"
icon="fa-truck"
attrs="{'invisible': [('picking_count', '=', 0)]}"
groups="stock.group_stock_user"
>
<field
name="picking_count"
widget="statinfo"
string="Transfers"
/>
</button>
</div>
<div class="oe_title">
<label for="name" string="Stock Request" />
<h1>
<field name="name" readonly="1" />
</h1>
</div>
<group>
<group>
<field
name="order_id"
readonly="1"
groups="stock_request.group_stock_request_order"
/>
<field name="product_id" />
<field name="expected_date" />
<field name="picking_policy" />
</group>
<group>
<field
name="warehouse_id"
widget="selection"
groups="stock.group_stock_multi_locations"
/>
<field
name="location_id"
groups="stock.group_stock_multi_locations"
domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]"
/>
<field
name="route_id"
options="{'no_create': True}"
groups="stock.group_stock_multi_locations"
/>
<field name="route_ids" invisible="1" />
<field
name="procurement_group_id"
groups="stock.group_adv_location"
/>
<field
name="company_id"
groups="base.group_multi_company"
options="{'no_create': True}"
/>
</group>
<group name="quantities">
<label for="product_uom_qty" />
<div>
<field name="product_uom_qty" class="oe_inline" />
<field
name="product_uom_id"
class="oe_inline"
options="{'no_open': True, 'no_create': True}"
groups="uom.group_uom"
/>
</div>
<field
name="qty_in_progress"
attrs="{'invisible': [('state', '=', 'draft')]}"
/>
<field
name="qty_done"
attrs="{'invisible': [('state', '=', 'draft')]}"
/>
<field
name="qty_cancelled"
attrs="{'invisible': [('state', '=', 'draft')]}"
/>
</group>
</group>
<notebook>
<!--Empty notebook to inherit pages from other related modules-->
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" />
<field name="activity_ids" widget="mail_activity" />
<field name="message_ids" widget="mail_thread" />
</div>
</form>
</field>
</record>
<record id="action_stock_request_form" model="ir.actions.act_window">
<field name="name">Stock Requests</field>
<field name="res_model">stock.request</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">tree,form,pivot</field>
<field name="context">{
'search_default_current_requests': 1,
'pivot_column_groupby': ['location_id'], 'pivot_row_groupby': ['product_id'], 'pivot_measures': ['product_uom_qty', 'qty_done'],
'search_default_draft': 1, 'search_default_open': 1
}</field>
<field name="view_id" ref="view_stock_request_tree" />
<field name="search_view_id" ref="stock_request_search" />
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a Stock Request.
</p>
</field>
</record>
</odoo>