mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[IMP] sale_stock_available_info_popup: black, isort, prettier
This commit is contained in:
@@ -3,21 +3,14 @@
|
||||
{
|
||||
"name": "Sale Stock Available Info Popup",
|
||||
"summary": "Adds an 'Available to promise' quantity to the popover shown "
|
||||
"in sale order line that display stock info of the product",
|
||||
"in sale order line that display stock info of the product",
|
||||
"author": "Tecnativa, Odoo Community Association (OCA)",
|
||||
"website": "https://github.com/OCA/stock-logistics-warehouse",
|
||||
"category": "Warehouse Management",
|
||||
"version": "12.0.1.0.1",
|
||||
"license": "AGPL-3",
|
||||
"depends": [
|
||||
"sale_stock_info_popup",
|
||||
"stock_available",
|
||||
],
|
||||
"data": [
|
||||
"views/sale_order_views.xml",
|
||||
],
|
||||
"qweb": [
|
||||
"static/src/xml/qty_at_date.xml",
|
||||
],
|
||||
"depends": ["sale_stock_info_popup", "stock_available",],
|
||||
"data": ["views/sale_order_views.xml",],
|
||||
"qweb": ["static/src/xml/qty_at_date.xml",],
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
@@ -7,22 +7,24 @@ from odoo import api, fields, models
|
||||
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_inherit = 'sale.order.line'
|
||||
_inherit = "sale.order.line"
|
||||
|
||||
immediately_usable_qty_today = fields.Float(
|
||||
compute='_compute_immediately_usable_qty_today')
|
||||
compute="_compute_immediately_usable_qty_today"
|
||||
)
|
||||
|
||||
@api.depends('product_id', 'product_uom_qty')
|
||||
@api.depends("product_id", "product_uom_qty")
|
||||
def _compute_immediately_usable_qty_today(self):
|
||||
qty_processed_per_product = defaultdict(lambda: 0)
|
||||
remaining = self.env['sale.order.line']
|
||||
remaining = self.env["sale.order.line"]
|
||||
for line in self.sorted(key=lambda r: r.sequence):
|
||||
if not line.display_qty_widget:
|
||||
remaining |= line
|
||||
continue
|
||||
product = line.product_id
|
||||
qty_processed = qty_processed_per_product[product.id]
|
||||
line.immediately_usable_qty_today = \
|
||||
line.immediately_usable_qty_today = (
|
||||
product.immediately_usable_qty - qty_processed
|
||||
)
|
||||
qty_processed_per_product[product.id] += line.product_uom_qty
|
||||
remaining.write({"immediately_usable_qty_today": False})
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- Copyright 2020 Tecnativa - Ernesto Tejeda
|
||||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
|
||||
<templates>
|
||||
<div t-extend="sale_stock.qtyAtDate">
|
||||
<t t-jquery="t[t-if^='widget.data.virtual_available_at_date']" t-operation="attributes">
|
||||
<attribute name="t-if">widget.data.immediately_usable_qty_today < widget.data.qty_to_deliver and !widget.data.is_mto</attribute>
|
||||
<t
|
||||
t-jquery="t[t-if^='widget.data.virtual_available_at_date']"
|
||||
t-operation="attributes"
|
||||
>
|
||||
<attribute
|
||||
name="t-if"
|
||||
>widget.data.immediately_usable_qty_today < widget.data.qty_to_deliver and !widget.data.is_mto</attribute>
|
||||
</t>
|
||||
</div>
|
||||
<div t-extend="sale_stock.QtyDetailPopOver">
|
||||
<t t-jquery="t[t-if*='!data.is_mto']" t-operation="append">
|
||||
<tr>
|
||||
<td><strong>Available to promise</strong></td>
|
||||
<td><t t-esc='data.immediately_usable_qty_today'/>
|
||||
<t t-esc='data.product_uom.data.display_name'/></td>
|
||||
<td>
|
||||
<strong>Available to promise</strong>
|
||||
</td>
|
||||
<td>
|
||||
<t t-esc='data.immediately_usable_qty_today' />
|
||||
<t t-esc='data.product_uom.data.display_name' />
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
@@ -5,113 +5,148 @@ from odoo.tests.common import SavepointCase
|
||||
|
||||
|
||||
class SaleStockAvailableInfoPopup(SavepointCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(SaleStockAvailableInfoPopup, cls).setUpClass()
|
||||
user_group_stock_user = cls.env.ref('stock.group_stock_user')
|
||||
cls.user_stock_user = cls.env['res.users'].create({
|
||||
'name': 'Pauline Poivraisselle',
|
||||
'login': 'pauline',
|
||||
'email': 'p.p@example.com',
|
||||
'notification_type': 'inbox',
|
||||
'groups_id': [(6, 0, [user_group_stock_user.id])]})
|
||||
cls.product = cls.env['product.product'].create({
|
||||
'name': 'Storable product',
|
||||
'type': 'product',
|
||||
})
|
||||
cls.stock_location = cls.env.ref('stock.stock_location_stock')
|
||||
cls.customers_location = cls.env.ref('stock.stock_location_customers')
|
||||
cls.suppliers_location = cls.env.ref('stock.stock_location_suppliers')
|
||||
cls.env['stock.quant'].create({
|
||||
'product_id': cls.product.id,
|
||||
'location_id': cls.stock_location.id,
|
||||
'quantity': 40.0})
|
||||
cls.picking_out = cls.env['stock.picking'].create({
|
||||
'picking_type_id': cls.env.ref('stock.picking_type_out').id,
|
||||
'location_id': cls.stock_location.id,
|
||||
'location_dest_id': cls.customers_location.id})
|
||||
cls.env['stock.move'].create({
|
||||
'name': 'a move',
|
||||
'product_id': cls.product.id,
|
||||
'product_uom_qty': 3.0,
|
||||
'product_uom': cls.product.uom_id.id,
|
||||
'picking_id': cls.picking_out.id,
|
||||
'location_id': cls.stock_location.id,
|
||||
'location_dest_id': cls.customers_location.id})
|
||||
cls.picking_in = cls.env['stock.picking'].create({
|
||||
'picking_type_id': cls.env.ref('stock.picking_type_in').id,
|
||||
'location_id': cls.suppliers_location.id,
|
||||
'location_dest_id': cls.stock_location.id})
|
||||
cls.env['stock.move'].create({
|
||||
'restrict_partner_id': cls.user_stock_user.partner_id.id,
|
||||
'name': 'another move',
|
||||
'product_id': cls.product.id,
|
||||
'product_uom_qty': 5.0,
|
||||
'product_uom': cls.product.uom_id.id,
|
||||
'picking_id': cls.picking_in.id,
|
||||
'location_id': cls.suppliers_location.id,
|
||||
'location_dest_id': cls.stock_location.id})
|
||||
user_group_stock_user = cls.env.ref("stock.group_stock_user")
|
||||
cls.user_stock_user = cls.env["res.users"].create(
|
||||
{
|
||||
"name": "Pauline Poivraisselle",
|
||||
"login": "pauline",
|
||||
"email": "p.p@example.com",
|
||||
"notification_type": "inbox",
|
||||
"groups_id": [(6, 0, [user_group_stock_user.id])],
|
||||
}
|
||||
)
|
||||
cls.product = cls.env["product.product"].create(
|
||||
{"name": "Storable product", "type": "product",}
|
||||
)
|
||||
cls.stock_location = cls.env.ref("stock.stock_location_stock")
|
||||
cls.customers_location = cls.env.ref("stock.stock_location_customers")
|
||||
cls.suppliers_location = cls.env.ref("stock.stock_location_suppliers")
|
||||
cls.env["stock.quant"].create(
|
||||
{
|
||||
"product_id": cls.product.id,
|
||||
"location_id": cls.stock_location.id,
|
||||
"quantity": 40.0,
|
||||
}
|
||||
)
|
||||
cls.picking_out = cls.env["stock.picking"].create(
|
||||
{
|
||||
"picking_type_id": cls.env.ref("stock.picking_type_out").id,
|
||||
"location_id": cls.stock_location.id,
|
||||
"location_dest_id": cls.customers_location.id,
|
||||
}
|
||||
)
|
||||
cls.env["stock.move"].create(
|
||||
{
|
||||
"name": "a move",
|
||||
"product_id": cls.product.id,
|
||||
"product_uom_qty": 3.0,
|
||||
"product_uom": cls.product.uom_id.id,
|
||||
"picking_id": cls.picking_out.id,
|
||||
"location_id": cls.stock_location.id,
|
||||
"location_dest_id": cls.customers_location.id,
|
||||
}
|
||||
)
|
||||
cls.picking_in = cls.env["stock.picking"].create(
|
||||
{
|
||||
"picking_type_id": cls.env.ref("stock.picking_type_in").id,
|
||||
"location_id": cls.suppliers_location.id,
|
||||
"location_dest_id": cls.stock_location.id,
|
||||
}
|
||||
)
|
||||
cls.env["stock.move"].create(
|
||||
{
|
||||
"restrict_partner_id": cls.user_stock_user.partner_id.id,
|
||||
"name": "another move",
|
||||
"product_id": cls.product.id,
|
||||
"product_uom_qty": 5.0,
|
||||
"product_uom": cls.product.uom_id.id,
|
||||
"picking_id": cls.picking_in.id,
|
||||
"location_id": cls.suppliers_location.id,
|
||||
"location_dest_id": cls.stock_location.id,
|
||||
}
|
||||
)
|
||||
|
||||
def test_immediately_usable_qty_today(self):
|
||||
self.picking_out.action_confirm()
|
||||
self.picking_in.action_assign()
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.env.ref('base.res_partner_1').id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'name': self.product.name,
|
||||
'product_id': self.product.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': self.product.uom_id.id,
|
||||
'price_unit': self.product.list_price
|
||||
}),
|
||||
],
|
||||
})
|
||||
so = self.env["sale.order"].create(
|
||||
{
|
||||
"partner_id": self.env.ref("base.res_partner_1").id,
|
||||
"order_line": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": self.product.name,
|
||||
"product_id": self.product.id,
|
||||
"product_uom_qty": 1,
|
||||
"product_uom": self.product.uom_id.id,
|
||||
"price_unit": self.product.list_price,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
line = so.order_line
|
||||
self.assertEqual(
|
||||
line.immediately_usable_qty_today,
|
||||
self.product.immediately_usable_qty,
|
||||
line.immediately_usable_qty_today, self.product.immediately_usable_qty,
|
||||
)
|
||||
|
||||
def test_immediately_usable_qty_today_similar_solines(self):
|
||||
"""Create a sale order containing three times the same product. The
|
||||
quantity available should be different for the 3 lines.
|
||||
"""
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.env.ref('base.res_partner_1').id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'sequence': 1,
|
||||
'name': self.product.name,
|
||||
'product_id': self.product.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': self.product.uom_id.id,
|
||||
'price_unit': self.product.list_price
|
||||
}),
|
||||
(0, 0, {
|
||||
'sequence': 2,
|
||||
'name': self.product.name,
|
||||
'product_id': self.product.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': self.product.uom_id.id,
|
||||
'price_unit': self.product.list_price
|
||||
}),
|
||||
(0, 0, {
|
||||
'sequence': 3,
|
||||
'name': self.product.name,
|
||||
'product_id': self.product.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': self.product.uom_id.id,
|
||||
'price_unit': self.product.list_price
|
||||
}),
|
||||
],
|
||||
})
|
||||
so = self.env["sale.order"].create(
|
||||
{
|
||||
"partner_id": self.env.ref("base.res_partner_1").id,
|
||||
"order_line": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"sequence": 1,
|
||||
"name": self.product.name,
|
||||
"product_id": self.product.id,
|
||||
"product_uom_qty": 5,
|
||||
"product_uom": self.product.uom_id.id,
|
||||
"price_unit": self.product.list_price,
|
||||
},
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"sequence": 2,
|
||||
"name": self.product.name,
|
||||
"product_id": self.product.id,
|
||||
"product_uom_qty": 5,
|
||||
"product_uom": self.product.uom_id.id,
|
||||
"price_unit": self.product.list_price,
|
||||
},
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"sequence": 3,
|
||||
"name": self.product.name,
|
||||
"product_id": self.product.id,
|
||||
"product_uom_qty": 5,
|
||||
"product_uom": self.product.uom_id.id,
|
||||
"price_unit": self.product.list_price,
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
so.order_line.mapped('immediately_usable_qty_today'),
|
||||
so.order_line.mapped("immediately_usable_qty_today"),
|
||||
[
|
||||
self.product.immediately_usable_qty,
|
||||
self.product.immediately_usable_qty - 5,
|
||||
self.product.immediately_usable_qty - 10,
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml version="1.0" ?>
|
||||
<!-- Copyright 2020 Tecnativa - Ernesto Tejeda
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
<odoo>
|
||||
<record id="view_order_form" model="ir.ui.view">
|
||||
<field name="name">sale.order.line.tree.inherit</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="inherit_id" ref="sale.view_order_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page/field[@name='order_line']/tree/field[@name='product_uom_qty']" position="after">
|
||||
<field name="immediately_usable_qty_today" invisible="1"/>
|
||||
<xpath
|
||||
expr="//page/field[@name='order_line']/tree/field[@name='product_uom_qty']"
|
||||
position="after"
|
||||
>
|
||||
<field name="immediately_usable_qty_today" invisible="1" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../../../../sale_stock_available_info_popup
|
||||
6
setup/sale_stock_available_info_popup/setup.py
Normal file
6
setup/sale_stock_available_info_popup/setup.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
||||
Reference in New Issue
Block a user