Use dedicated model for products and lots by locations
issue9874 review314911002
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
* Add products_by_locations Model
|
||||
|
||||
Version 5.8.0 - 2020-11-02
|
||||
* Bug fixes (see mercurial logs for details)
|
||||
* Remove support for Python 3.5
|
||||
|
||||
@@ -25,6 +25,7 @@ def register():
|
||||
location.Party,
|
||||
location.PartyLocation,
|
||||
location.ProductsByLocationsContext,
|
||||
location.ProductsByLocations,
|
||||
location.LocationLeadTime,
|
||||
move.Move,
|
||||
shipment.ShipmentIn,
|
||||
|
||||
+63
-2
@@ -4,10 +4,12 @@ import datetime
|
||||
import operator
|
||||
from decimal import Decimal
|
||||
|
||||
from sql import Column
|
||||
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import (
|
||||
ModelView, ModelSQL, MatchMixin, ValueMixin, DeactivableMixin, fields,
|
||||
sequence_ordered, tree)
|
||||
Model, ModelView, ModelSQL, MatchMixin, ValueMixin, DeactivableMixin,
|
||||
fields, sequence_ordered, tree)
|
||||
from trytond import backend
|
||||
from trytond.cache import Cache
|
||||
from trytond.pyson import Eval, If
|
||||
@@ -710,6 +712,65 @@ class ProductsByLocationsContext(ModelView):
|
||||
return self.forecast_date
|
||||
|
||||
|
||||
class ProductsByLocations(DeactivableMixin, ModelSQL, ModelView):
|
||||
"Products by Locations"
|
||||
__name__ = 'stock.products_by_locations'
|
||||
|
||||
product = fields.Many2One('product.product', "Product")
|
||||
quantity = fields.Function(
|
||||
fields.Float(
|
||||
"Quantity", digits=(16, Eval('default_uom_digits', 2)),
|
||||
depends=['default_uom_digits']),
|
||||
'get_product', searcher='search_product')
|
||||
forecast_quantity = fields.Function(
|
||||
fields.Float(
|
||||
"Forecast Quantity", digits=(16, Eval('default_uom_digits', 2)),
|
||||
depends=['default_uom_digits']),
|
||||
'get_product', searcher='search_product')
|
||||
default_uom = fields.Function(
|
||||
fields.Many2One('product.uom', "Default UOM"),
|
||||
'get_product', searcher='search_product')
|
||||
default_uom_digits = fields.Function(
|
||||
fields.Integer("Default UOM Digits"), 'get_product')
|
||||
cost_value = fields.Function(
|
||||
fields.Numeric("Cost Value"), 'get_product')
|
||||
consumable = fields.Function(
|
||||
fields.Boolean("Consumable"), 'get_product',
|
||||
searcher='search_product')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._order.insert(0, ('product', 'ASC'))
|
||||
|
||||
@classmethod
|
||||
def table_query(cls):
|
||||
pool = Pool()
|
||||
Product = pool.get('product.product')
|
||||
product = Product.__table__()
|
||||
columns = []
|
||||
for fname, field in cls._fields.items():
|
||||
if not hasattr(field, 'set'):
|
||||
if (isinstance(field, fields.Many2One)
|
||||
and field.get_target() == Product):
|
||||
column = Column(product, 'id')
|
||||
else:
|
||||
column = Column(product, fname)
|
||||
columns.append(column.as_(fname))
|
||||
return product.select(*columns)
|
||||
|
||||
def get_product(self, name):
|
||||
value = getattr(self.product, name)
|
||||
if isinstance(value, Model):
|
||||
value = value.id
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def search_product(cls, name, clause):
|
||||
nested = clause[0].lstrip(name)
|
||||
return [('product.' + name + nested,) + tuple(clause[1:])]
|
||||
|
||||
|
||||
class LocationLeadTime(sequence_ordered(), ModelSQL, ModelView, MatchMixin):
|
||||
'Location Lead Time'
|
||||
__name__ = 'stock.location.lead_time'
|
||||
|
||||
+17
-19
@@ -67,11 +67,16 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="name">products_by_locations_context_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_products_by_locations">
|
||||
<field name="name">Products</field>
|
||||
<field name="res_model">product.product</field>
|
||||
<field name="context" eval="{'locations': Eval('active_ids')}"
|
||||
pyson="1"/>
|
||||
<record model="ir.ui.view" id="products_by_locations_view_list">
|
||||
<field name="model">stock.products_by_locations</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">products_by_locations_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_products_by_locations_relate">
|
||||
<field name="name">Products by Locations</field>
|
||||
<field name="res_model">stock.products_by_locations</field>
|
||||
<field name="context" eval="{'locations': Eval('active_ids')}" pyson="1"/>
|
||||
<field name="domain"
|
||||
eval="['OR', ('quantity', '!=', 0.0), ('forecast_quantity', '!=', 0.0)]"
|
||||
pyson="1"/>
|
||||
@@ -80,27 +85,20 @@ this repository contains the full copyright notices and license terms. -->
|
||||
pyson="1"/>
|
||||
<field name="context_model">stock.products_by_locations.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_product_form_view2">
|
||||
<record model="ir.action.act_window.view" id="act_products_by_locations_relate_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="product_view_tree_qty"/>
|
||||
<field name="act_window" ref="act_products_by_locations"/>
|
||||
<field name="view" ref="products_by_locations_view_list"/>
|
||||
<field name="act_window" ref="act_products_by_locations_relate"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_product_form_view">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="product.product_view_form"/>
|
||||
<field name="act_window" ref="act_products_by_locations"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword"
|
||||
id="act_products_by_locations_keyword1">
|
||||
<record model="ir.action.keyword" id="act_products_by_locations_relate_keyword1">
|
||||
<field name="keyword">tree_open</field>
|
||||
<field name="model">stock.location,-1</field>
|
||||
<field name="action" ref="act_products_by_locations"/>
|
||||
<field name="action" ref="act_products_by_locations_relate"/>
|
||||
</record>
|
||||
<record model="ir.action.keyword"
|
||||
id="act_products_by_locations_keyword2">
|
||||
<record model="ir.action.keyword" id="act_products_by_locations_relate_keyword2">
|
||||
<field name="keyword">form_relate</field>
|
||||
<field name="model">stock.location,-1</field>
|
||||
<field name="action" ref="act_products_by_locations"/>
|
||||
<field name="action" ref="act_products_by_locations_relate"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.model.access" id="access_location">
|
||||
|
||||
+13
-8
@@ -23,7 +23,7 @@ from trytond.transaction import Transaction
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.tools import grouped_slice
|
||||
|
||||
from trytond.modules.product import round_price
|
||||
from trytond.modules.product import round_price, price_digits
|
||||
|
||||
from .exceptions import ProductCostPriceError
|
||||
from .move import StockMixin
|
||||
@@ -111,14 +111,19 @@ class Template(metaclass=PoolMeta):
|
||||
|
||||
class Product(StockMixin, object, metaclass=PoolMeta):
|
||||
__name__ = "product.product"
|
||||
quantity = fields.Function(fields.Float('Quantity',
|
||||
help="The amount of stock in the location."),
|
||||
quantity = fields.Function(fields.Float(
|
||||
"Quantity", digits=(16, Eval('default_uom_digits', 2)),
|
||||
depends=['default_uom_digits'],
|
||||
help="The amount of stock in the location."),
|
||||
'get_quantity', searcher='search_quantity')
|
||||
forecast_quantity = fields.Function(fields.Float('Forecast Quantity',
|
||||
help="The amount of stock expected to be in the location."),
|
||||
forecast_quantity = fields.Function(fields.Float(
|
||||
"Forecast Quantity", digits=(16, Eval('default_uom_digits', 2)),
|
||||
depends=['default_uom_digits'],
|
||||
help="The amount of stock expected to be in the location."),
|
||||
'get_quantity', searcher='search_quantity')
|
||||
cost_value = fields.Function(fields.Numeric('Cost Value',
|
||||
help="The value of the stock in the location."),
|
||||
cost_value = fields.Function(fields.Numeric(
|
||||
"Cost Value", digits=price_digits,
|
||||
help="The value of the stock in the location."),
|
||||
'get_cost_value')
|
||||
|
||||
@classmethod
|
||||
@@ -149,7 +154,7 @@ class Product(StockMixin, object, metaclass=PoolMeta):
|
||||
for product in cls.browse(products):
|
||||
# The product may not have a cost price
|
||||
if product.cost_price is not None:
|
||||
cost_values[product.id] = (
|
||||
cost_values[product.id] = round_price(
|
||||
Decimal(str(product.quantity)) * product.cost_price)
|
||||
return cost_values
|
||||
|
||||
|
||||
@@ -3,13 +3,6 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="product_view_tree_qty">
|
||||
<field name="model">product.product</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="20"/>
|
||||
<field name="name">product_tree_qty</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.wizard" id="wizard_recompute_cost_price">
|
||||
<field name="name">Recompute Cost Price</field>
|
||||
<field name="wiz_name">product.recompute_cost_price</field>
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="quantity"/>
|
||||
<field name="forecast_quantity"/>
|
||||
<field name="default_uom"/>
|
||||
<field name="product" expand="1"/>
|
||||
<field name="quantity" symbol="default_uom"/>
|
||||
<field name="forecast_quantity" symbol="default_uom"/>
|
||||
<field name="cost_value" sum="Cost Value"/>
|
||||
<field name="consumable" tree_invisible="1"/>
|
||||
</tree>
|
||||
Reference in New Issue
Block a user