Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[10.0][ADD] stock_no_negative: Add setup #3

Open
wants to merge 8 commits into
base: 10-stock_no_negative
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions setup/stock_no_negative/odoo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)
1 change: 1 addition & 0 deletions setup/stock_no_negative/odoo/addons/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__import__('pkg_resources').declare_namespace(__name__)
1 change: 1 addition & 0 deletions setup/stock_no_negative/odoo/addons/stock_no_negative
6 changes: 6 additions & 0 deletions setup/stock_no_negative/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import setuptools

setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)
58 changes: 58 additions & 0 deletions stock_no_negative/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3

=======================
Stock Disallow Negative
=======================

By default, Odoo allows negative stock. The advantage of negative stock is that, if some stock levels are wrong in the ERP, you will not be blocked when validating the picking for a customer... so you will still be able to ship the products on time (it's an example !). The problem is that, after you forced the stock level to negative, you are supposed to fix the stock level later via an inventory ; but this action is often forgotten by users, so you end up with negative stock levels in your ERP and it can stay like this forever (or at least until the next full inventory).

If you disallow negative stock in Odoo with this module, you will be blocked when trying to validate a stock operation that will set the stock level of a product as negative. So you will have to fix the wrong stock level of that product without delay, in order to validate the stock operation in Odoo... you can't forget it anymore !

Configuration
=============

By default, the stockable products will not be allowed to have a negative stock. If you want to make some exceptions for some products or some product categories, you can activate the option *Allow Negative Stock* on some products or some products categories.

Usage
=====

When you validate a stock operation (a stock move, a picking, a manufacturing order, etc.) that will set the stock level of a stockable product as negative, you will be blocked by an error message. The consumable products can still have a negative stock level.

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/154/10.0

Bug Tracker
===========

Bugs are tracked on `GitHub Issues
<https://github.com/OCA/stock-logistics-workflow/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.

Credits
=======

Contributors
------------

* Alexis de Lattre <[email protected]>
* Eficent Business and IT Consulting Services S.L. <[email protected]>
* Serpent Consulting Services Pvt. Ltd. <[email protected]>

Maintainer
----------

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

This module is maintained by the OCA.

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.

To contribute to this module, please visit https://odoo-community.org.
6 changes: 6 additions & 0 deletions stock_no_negative/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# © 2015-2016 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import models
18 changes: 18 additions & 0 deletions stock_no_negative/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# © 2015-2016 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).


{
'name': 'Stock Disallow Negative',
'version': '10.0.1.0.2',
'category': 'Inventory, Logistic, Storage',
'license': 'AGPL-3',
'summary': 'Disallow negative stock levels by default',
'author': 'Akretion,Odoo Community Association (OCA)',
'website': 'http://www.akretion.com',
'depends': ['stock'],
'data': ['views/product.xml'],
'installable': True,
}
59 changes: 59 additions & 0 deletions stock_no_negative/i18n/fr.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * stock_no_negative
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-10-07 09:43+0000\n"
"PO-Revision-Date: 2015-10-07 09:43+0000\n"
"Last-Translator: Alexis de Lattre <[email protected]>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

#. module: stock_no_negative
#: field:product.category,allow_negative_stock:0
#: field:product.template,allow_negative_stock:0
msgid "Allow Negative Stock"
msgstr "Autoriser le stock négatif"

#. module: stock_no_negative
#: help:product.category,allow_negative_stock:0
msgid "Allow negative stock levels for the stockable products attached to this category. The options doesn't apply to products attached to sub-categories of this category."
msgstr "Autorise les niveaux de stock négatif pour les articles stockables attachés à cette catégorie. Cette option ne s'applique pas aux articles attachés à des sous-catégories de cette catégorie."

#. module: stock_no_negative
#: help:product.template,allow_negative_stock:0
msgid "If this option is not active on this product nor on its product category and that this product is a stockable product, then the validation of the related stock moves will be blocked if the stock level becomes negative with the stock move."
msgstr "Si cette option n'est pas activée sur cet article ni sur la catégorie à laquelle il est rattaché et que cet article est un produit stockable, alors la validation des mouvements de stock sera bloquée si le niveau de stock devient négatif avec ce mouvement de stock."

#. module: stock_no_negative
#: model:ir.model,name:stock_no_negative.model_product_category
msgid "Product Category"
msgstr "Catégorie d'articles"

#. module: stock_no_negative
#: model:ir.model,name:stock_no_negative.model_product_template
msgid "Product Template"
msgstr "Modèle d'article"

#. module: stock_no_negative
#: model:ir.model,name:stock_no_negative.model_stock_quant
msgid "Quants"
msgstr "Quants"

#. module: stock_no_negative
#: view:product.template:stock_no_negative.product_template_form_view
msgid "Stock and Expected Variations"
msgstr "Stock et variations prévues"

#. module: stock_no_negative
#: code:addons/stock_no_negative/stock_no_negative.py:60
#, python-format
msgid "You cannot valide this stock operation because the stock level of the product '%s' would become negative on the stock location '%s' and negative stock is not allowed for this product."
msgstr "Vous ne pouvez pas valider cette opération de stock car le niveau de stock de l'article '%s' deviendrait négatif sur l'emplacement de stock '%s' et le stock négatif n'est pas autorisé pour cet article."

7 changes: 7 additions & 0 deletions stock_no_negative/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# © 2015-2016 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import product
from . import stock_quant
27 changes: 27 additions & 0 deletions stock_no_negative/models/product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# © 2015-2016 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import models, fields


class ProductCategory(models.Model):
_inherit = 'product.category'

allow_negative_stock = fields.Boolean(
string='Allow Negative Stock',
help="Allow negative stock levels for the stockable products "
"attached to this category. The options doesn't apply to products "
"attached to sub-categories of this category.")


class ProductTemplate(models.Model):
_inherit = "product.template"

allow_negative_stock = fields.Boolean(
string='Allow Negative Stock',
help="If this option is not active on this product nor on its "
"product category and that this product is a stockable product, "
"then the validation of the related stock moves will be blocked if "
"the stock level becomes negative with the stock move.")
34 changes: 34 additions & 0 deletions stock_no_negative/models/stock_quant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# © 2015-2017 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import models, api, _
from odoo.exceptions import ValidationError
from odoo.tools import float_compare


class StockQuant(models.Model):
_inherit = 'stock.quant'

@api.multi
@api.constrains('product_id', 'qty')
def check_negative_qty(self):
p = self.env['decimal.precision'].precision_get(
'Product Unit of Measure')
for quant in self:
if (
float_compare(quant.qty, 0, precision_digits=p) == -1 and
quant.product_id.type == 'product' and
not quant.product_id.allow_negative_stock and
not quant.product_id.categ_id.allow_negative_stock):
msg_add = ''
if quant.lot_id:
msg_add = _(" lot '%s'") % quant.lot_id.name_get()[0][1]
raise ValidationError(_(
"You cannot validate this stock operation because the "
"stock level of the product '%s'%s would become negative "
"(%s) on the stock location '%s' and negative stock is "
"not allowed for this product.") % (
quant.product_id.name, msg_add, quant.qty,
quant.location_id.complete_name))
9 changes: 9 additions & 0 deletions stock_no_negative/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# © 2015-2016 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <[email protected]>
# © 2016 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# © 2016 Serpent Consulting Services Pvt. Ltd. (<http://www.serpentcs.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import test_stock_no_negative
91 changes: 91 additions & 0 deletions stock_no_negative/tests/test_stock_no_negative.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# © 2015-2016 Akretion (http://www.akretion.com)
# @author Alexis de Lattre <[email protected]>
# © 2016 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# © 2016 Serpent Consulting Services Pvt. Ltd. (<http://www.serpentcs.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).


from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError


class TestStockNoNegative(TransactionCase):

def setUp(self):
super(TestStockNoNegative, self).setUp()
self.product_model = self.env['product.product']
self.product_ctg_model = self.env['product.category']
self.picking_type_id = self.env.ref('stock.picking_type_out')
self.location_id = self.env.ref('stock.stock_location_stock')
self.location_dest_id = self.env.ref('stock.stock_location_customers')
# Create product category
self.product_ctg = self._create_product_category()
# Create a Product
self.product = self._create_product('test_product1')
self._create_picking()

def _create_product_category(self):
product_ctg = self.product_ctg_model.create({
'name': 'test_product_ctg',
})
return product_ctg

def _create_product(self, name):
product = self.product_model.create({
'name': name,
'categ_id': self.product_ctg.id,
'type': 'product',
})
return product

def _create_picking(self):
self.stock_picking = self.env['stock.picking'].create({
'picking_type_id': self.picking_type_id.id,
'move_type': 'direct',
'location_id': self.location_id.id,
'location_dest_id': self.location_dest_id.id
})

self.stock_move = self.env['stock.move'].create({
'name': 'Test Move',
'product_id': self.product.id,
'product_uom_qty': 100.0,
'product_uom': self.product.uom_id.id,
'picking_id': self.stock_picking.id,
'state': 'draft',
'location_id': self.location_id.id,
'location_dest_id': self.location_dest_id.id
})

def test_check_constrains(self):
"""Assert that constraint is raised when user
tries to validate the stock operation which would
make the stock level of the product negative """
self.stock_picking.action_confirm()
self.stock_picking.action_assign()
self.stock_picking.force_assign()
self.stock_picking.do_new_transfer()
self.stock_immediate_transfer = self.env['stock.immediate.transfer'].\
create({'pick_id': self.stock_picking.id})
with self.assertRaises(ValidationError):
self.stock_immediate_transfer.process()

def test_true_allow_negative_stock(self):
"""Assert that negative stock levels are allowed when
the allow_negative_stock is set active in the product"""
self.product.product_tmpl_id.write({'allow_negative_stock': True})
self.stock_picking.action_confirm()
self.stock_picking.action_assign()
self.stock_picking.force_assign()
self.stock_picking.do_new_transfer()
self.stock_immediate_transfer = self.env['stock.immediate.transfer'].\
create({'pick_id': self.stock_picking.id})
self.stock_immediate_transfer.process()
quant = self.env['stock.quant'].search([('product_id', '=',
self.product.id),
('location_id', '=',
self.location_id.id)])
self.assertEqual(quant.qty,
-self.stock_move.product_uom_qty)
34 changes: 34 additions & 0 deletions stock_no_negative/views/product.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
© 2015-2016 Akretion (http://www.akretion.com/)
@author Alexis de Lattre <[email protected]>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->

<odoo>

<record id="view_template_property_form" model="ir.ui.view">
<field name="name">stock_no_negative.product.template.form</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="stock.view_template_property_form" />
<field name="arch" type="xml">
<field name="categ_id" position="after">
<field name="allow_negative_stock"
attrs="{'invisible': [('type', '!=', 'product')]}"/>
</field>
</field>
</record>

<record id="product_category_form_view_inherit" model="ir.ui.view">
<field name="name">stock_no_negative.product.category.form</field>
<field name="model">product.category</field>
<field name="inherit_id" ref="stock.product_category_form_view_inherit"/>
<field name="arch" type="xml">
<field name="removal_strategy_id" position="after">
<field name="allow_negative_stock"
attrs="{'invisible': [('type', '=', 'view')]}"/>
</field>
</field>
</record>

</odoo>