Commit 3530f7c6 authored by Jérome Perrin's avatar Jérome Perrin

Stock report valuation

Extend stock report dialog to allow choosing a (simple) valuation method

![stock report dialog screenshot](/uploads/2e0fa7c420954c3f84260605b3a6930e/image.png)

which be displayed in a new column, showing the inventory value for each line

See merge request nexedi/erp5!1203
parents ae3d5330 85350657
Pipeline #12146 failed with stage
in 0 seconds
......@@ -92,6 +92,7 @@
<string>listbox_quantity_unit</string>
<string>listbox_variation_category_item_list</string>
<string>listbox_aggregate_title_list</string>
<string>listbox_total_price</string>
</list>
</value>
</item>
......@@ -103,6 +104,7 @@
<string>your_section_category</string>
<string>your_at_date</string>
<string>your_simulation_period</string>
<string>your_currency</string>
</list>
</value>
</item>
......
......@@ -145,6 +145,10 @@
<string>aggregate_quantity_list</string>
<string>Item Quantity List</string>
</tuple>
<tuple>
<string>total_price</string>
<string>Inventory Value</string>
</tuple>
</list>
</value>
</item>
......@@ -199,7 +203,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: field.get_orig_value(\'columns\') + (context.REQUEST.form.get("item_stock") == 1 and [(\'aggregate_title_list\', \'Aggregated Items\')] or [])</string> </value>
<value> <string>python: field.get_orig_value(\'columns\') + (context.REQUEST.form.get("item_stock") == 1 and [(\'aggregate_title_list\', \'Aggregated Items\')] or []) + (context.REQUEST.form.get("inventory_valuation_method") and [(\'total_price\', \'Inventory Value\')] or [])</string> </value>
</item>
</dictionary>
</pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>precision</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>listbox_total_price</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>precision</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_total_price</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewTradeFieldLibrary</string> </value>
</item>
<item>
<key> <string>precision</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Inventory Value</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: cell.InventoryListBrain_getInventoryValuatedTotalPrice(request[\'inventory_valuation_method\'])</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>request/precision | python: 2</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>your_currency</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_report_mode_currency</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewTradeFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -111,6 +111,7 @@
<string>your_negative_stock</string>
<string>your_zero_stock</string>
<string>your_item_stock</string>
<string>your_inventory_valuation_method</string>
</list>
</value>
</item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ListField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>your_inventory_valuation_method</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>required_not_found</string> </key>
<value> <string>Input is required but no input given.</string> </value>
</item>
<item>
<key> <string>unknown_selection</string> </key>
<value> <string>You selected an item that was not in the list.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra_item</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>first_item</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>items</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>size</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra_item</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>first_item</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>items</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>size</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Price to apply to each product</string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra_item</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>first_item</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>items</string> </key>
<value>
<list>
<tuple>
<string></string>
<string></string>
</tuple>
<tuple>
<string>Default Purchase Price</string>
<string>default_purchase_price</string>
</tuple>
<tuple>
<string>Default Internal Price</string>
<string>default_internal_price</string>
</tuple>
<tuple>
<string>Default Sales Price</string>
<string>default_sale_price</string>
</tuple>
</list>
</value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>size</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Valuation Method</string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: [(context.Base_translateString(label), value) for (label, value) in field.get_orig_value(\'items\')]</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -304,6 +304,7 @@
<string>my_view_mode_preferred_tax_use_list</string>
<string>my_report_mode_node_category</string>
<string>my_view_mode_ledger</string>
<string>my_report_mode_currency</string>
</list>
</value>
</item>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ListField" module="Products.Formulator.StandardFields"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>id</string> </key>
<value> <string>my_report_mode_currency</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
<item>
<key> <string>required_not_found</string> </key>
<value> <string>Input is required but no input given.</string> </value>
</item>
<item>
<key> <string>unknown_selection</string> </key>
<value> <string>You selected an item that was not in the list.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra_item</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>first_item</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>items</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>size</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra_item</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>first_item</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>items</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>required</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>size</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>alternate_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>extra_item</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>first_item</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>items</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>required</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>size</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Currency</string> </value>
</item>
<item>
<key> <string>unicode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>whitespace_preserve</string> </key>
<value> <int>0</int> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: context.Base_getCurrencyForSection(request[\'section_category\'] or request[\'node_category\'])</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python:here.CurrencyModule_getCurrencyItemList()</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
request = container.REQUEST
portal = context.getPortalObject()
# set request `precision` for listbox's total price field if not already set
if request.get('precision') is None:
request.set('precision', 3) # fallback value to search only once if nothing is defined
# Search an organisation's accounting currency to display currencies in this precision.
organisation_search_kw = {
'portal_type': 'Organisation',
}
if request.get('section_category'):
organisation_search_kw['uid'] = portal.Base_getSectionUidListForSectionCategory(request['section_category'])
else:
organisation_search_kw['site_uid'] = portal.portal_categories.restrictedTraverse(request['node_category']).getUid()
for brain in portal.portal_catalog(**organisation_search_kw):
currency_relative_url = brain.getObject().getPriceCurrency()
if currency_relative_url:
request.set('precision', context.getQuantityPrecisionFromResource(currency_relative_url))
break
def getPriceFromDefaultSupplyLine(brain, supply_line_id):
# TODO: support variations ? (at same time this approach is intentionally super simple)
# XXX what if this supply line's currency does not match the default accounting currency ?
resource = brain.getResourceValue()
base_price = None
supply_line = getattr(resource, supply_line_id, None)
if supply_line is not None:
base_price = supply_line.getBasePrice()
priced_quantity = supply_line.getPricedQuantity()
if priced_quantity and supply_line.getQuantityUnit():
priced_quantity = resource.convertQuantity(
priced_quantity,
supply_line.getQuantityUnit(),
resource.getQuantityUnit())
if base_price is not None:
base_price /= priced_quantity
if base_price is None:
return None
return brain.inventory * base_price
if inventory_valuation_method:
supply_line_id_mapping = {
'default_purchase_price': 'default_psl',
'default_internal_price': 'default_isl',
'default_sale_price': 'default_ssl',
}
return getPriceFromDefaultSupplyLine(
context,
supply_line_id_mapping[inventory_valuation_method],
)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>inventory_valuation_method=\'\', **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>InventoryListBrain_getInventoryValuatedTotalPrice</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -102,6 +102,14 @@ class TestTradeReports(ERP5ReportTestCase):
title=group_id,
reference=group_id,
id=group_id)
# currencies
if not self.portal.currency_module.has_key('EUR'):
self.portal.currency_module.newContent(
portal_type='Currency',
id='EUR',
base_unit_quantity=0.01,
).validate()
# create organisations (with no organisation member of g3)
if not self.organisation_module.has_key('Organisation_1'):
self.portal.organisation_module.newContent(
......@@ -111,6 +119,7 @@ class TestTradeReports(ERP5ReportTestCase):
id='Organisation_1',
group='g1',
site='demo_site_A',
price_currency_value=self.portal.currency_module.EUR,
default_email_coordinate_text='organisation1@example.com',
default_telephone_coordinate_text='11111',
default_address_street_address='1 Organisation Street',
......@@ -133,41 +142,59 @@ class TestTradeReports(ERP5ReportTestCase):
id='Organisation_3',)
# create unit categories
if not self.portal_categories.quantity_unit.has_key('mass'):
self.portal_categories.quantity_unit.newContent(
portal_type='Category',
id='mass')
for unit_id in ('kg', 'g',):
if not self.portal_categories['quantity_unit'].has_key(unit_id):
self.portal_categories.quantity_unit.newContent(
if not self.portal_categories.quantity_unit.mass.has_key(unit_id):
self.portal_categories.quantity_unit.mass.newContent(
portal_type='Category',
title=unit_id.title(),
reference=unit_id,
id=unit_id)
# and corresponding unit conversion group
if not self.portal.quantity_unit_conversion_module.has_key('mass_conversion_group'):
self.portal.quantity_unit_conversion_module.newContent(
portal_type='Quantity Unit Conversion Group',
id='mass_conversion_group',
quantity_unit_value=self.portal_categories.quantity_unit.mass.g,
).validate()
self.portal.quantity_unit_conversion_module.mass_conversion_group.newContent(
portal_type='Quantity Unit Conversion Definition',
id='ton',
quantity_unit_value=self.portal_categories.quantity_unit.mass.kg,
quantity=1000,
).validate()
self.tic()
# Create resources
module = self.portal.product_module
if not module.has_key('product_B'):
module.newContent(
portal_type='Product',
id='product_B',
title='product_B',
reference='ref 1',
quantity_unit='kg'
)
if not module.has_key('product_A'):
module.newContent(
portal_type='Product',
id='product_A',
title='product_A',
reference='ref 2',
quantity_unit='g'
)
if not module.has_key('product_C'):
module.newContent(
portal_type='Product',
id='product_C',
title='variated product',
reference='ref 3',
variation_base_category_list=['colour'],
colour_list=['colour1', 'colour2'],
)
self.portal.product_module.newContent(
portal_type='Product',
id='product_A',
title='product_A',
reference='ref 2',
quantity_unit_list=('mass/g', 'mass/kg'),
default_purchase_supply_line_base_price=3,
default_internal_supply_line_base_price=5,
default_sale_supply_line_base_price=7,
)
self.portal.product_module.newContent(
portal_type='Product',
id='product_B',
title='product_B',
reference='ref 1',
quantity_unit='mass/kg'
)
self.portal.product_module.newContent(
portal_type='Product',
id='product_C',
title='variated product',
reference='ref 3',
variation_base_category_list=['colour'],
colour_list=['colour1', 'colour2'],
)
if not self.portal.service_module.has_key('service_a'):
self.portal.service_module.newContent(
portal_type='Service',
......@@ -938,7 +965,7 @@ class TestTradeReports(ERP5ReportTestCase):
self.tic()
def _createConfirmedSalePackingListForStockReportTest(self):
def _createConfirmedSalePackingListForStockReportTest(self, quantity=1, quantity_unit_value=None):
confirmed_sale_packing_list = self.portal.sale_packing_list_module.newContent(
portal_type='Sale Packing List',
title='%s 1' % self.id(),
......@@ -954,8 +981,9 @@ class TestTradeReports(ERP5ReportTestCase):
confirmed_sale_packing_list.newContent(
portal_type='Sale Packing List Line',
resource_value=self.portal.product_module.product_A,
quantity=1,
quantity=quantity,
price=10,
quantity_unit_value=quantity_unit_value,
)
confirmed_sale_packing_list.confirm()
......@@ -1093,6 +1121,31 @@ class TestTradeReports(ERP5ReportTestCase):
inventory=66,
quantity_unit='')
def testStockReport_unit_conversion(self):
self._createConfirmedSalePackingListForStockReportTest(
quantity=0.5,
quantity_unit_value=self.portal.portal_categories.quantity_unit.mass.kg,
)
request = self.portal.REQUEST
request.form['at_date'] = DateTime(2007, 3, 3)
request.form['node_category'] = 'site/demo_site_A'
request.form['simulation_period'] = 'future'
line_list = self.portal.inventory_module.Base_viewStockReportBySite.listbox.\
get_value('default',
render_format='list', REQUEST=self.portal.REQUEST)
data_line_list = [l for l in line_list if l.isDataLine()]
self.assertEqual(1, len(data_line_list))
self.checkLineProperties(
data_line_list[0],
resource_title='product_A',
resource_reference='ref 2',
variation_category_item_list=[],
inventory=500,
quantity_unit='G')
def _createInventoryForStockReportWithPositiveOrNegativeOrZeroStockTest(self):
# Create inventories
self._makeOneInventory(
......@@ -1400,6 +1453,169 @@ class TestTradeReports(ERP5ReportTestCase):
self.assertEqual(0, len(data_line_list))
def testStockReport_valuation_method_default_default_purchase_price(self):
self._createConfirmedSalePackingListForStockReportTest()
request = self.portal.REQUEST
request.form['at_date'] = DateTime(2007, 3, 3)
request.form['node_category'] = 'site/demo_site_A'
request.form['simulation_period'] = 'future'
request.form['inventory_valuation_method'] = 'default_purchase_price'
line_list = self.portal.inventory_module.Base_viewStockReportBySite.listbox.\
get_value('default',
render_format='list', REQUEST=self.portal.REQUEST)
data_line_list = [l for l in line_list if l.isDataLine()]
self.assertEqual(1, len(data_line_list))
data_line = data_line_list[0]
self.assertEqual(
data_line.column_id_list,
['resource_title', 'resource_reference', 'variation_category_item_list', 'inventory', 'quantity_unit', 'total_price'])
self.checkLineProperties(
data_line_list[0],
resource_title='product_A',
resource_reference='ref 2',
variation_category_item_list=[],
inventory=1,
quantity_unit='G',
total_price=3,
)
# listbox_total_price is an editable field using this for precision
self.assertEqual(self.portal.REQUEST.get('precision'), 2)
def testStockReport_valuation_method_default_default_internal_price(self):
self._createConfirmedSalePackingListForStockReportTest()
request = self.portal.REQUEST
request.form['at_date'] = DateTime(2007, 3, 3)
request.form['node_category'] = 'site/demo_site_A'
request.form['simulation_period'] = 'future'
request.form['inventory_valuation_method'] = 'default_internal_price'
line_list = self.portal.inventory_module.Base_viewStockReportBySite.listbox.\
get_value('default',
render_format='list', REQUEST=self.portal.REQUEST)
data_line_list = [l for l in line_list if l.isDataLine()]
self.assertEqual(1, len(data_line_list))
data_line = data_line_list[0]
self.assertEqual(
data_line.column_id_list,
['resource_title', 'resource_reference', 'variation_category_item_list', 'inventory', 'quantity_unit', 'total_price'])
self.checkLineProperties(
data_line_list[0],
resource_title='product_A',
resource_reference='ref 2',
variation_category_item_list=[],
inventory=1,
quantity_unit='G',
total_price=5,
)
def testStockReport_valuation_method_default_default_sales_price(self):
self._createConfirmedSalePackingListForStockReportTest()
request = self.portal.REQUEST
request.form['at_date'] = DateTime(2007, 3, 3)
request.form['node_category'] = 'site/demo_site_A'
request.form['simulation_period'] = 'future'
request.form['inventory_valuation_method'] = 'default_sale_price'
line_list = self.portal.inventory_module.Base_viewStockReportBySite.listbox.\
get_value('default',
render_format='list', REQUEST=self.portal.REQUEST)
data_line_list = [l for l in line_list if l.isDataLine()]
self.assertEqual(1, len(data_line_list))
data_line = data_line_list[0]
self.assertEqual(
data_line.column_id_list,
['resource_title', 'resource_reference', 'variation_category_item_list', 'inventory', 'quantity_unit', 'total_price'])
self.checkLineProperties(
data_line_list[0],
resource_title='product_A',
resource_reference='ref 2',
variation_category_item_list=[],
inventory=1,
quantity_unit='G',
total_price=7,
)
def testStockReport_valuation_and_quantity_unit_conversion(self):
self._createConfirmedSalePackingListForStockReportTest(
quantity=0.5,
quantity_unit_value=self.portal.portal_categories.quantity_unit.mass.kg,
)
request = self.portal.REQUEST
request.form['at_date'] = DateTime(2007, 3, 3)
request.form['node_category'] = 'site/demo_site_A'
request.form['simulation_period'] = 'future'
request.form['inventory_valuation_method'] = 'default_purchase_price'
line_list = self.portal.inventory_module.Base_viewStockReportBySite.listbox.\
get_value('default',
render_format='list', REQUEST=self.portal.REQUEST)
data_line_list = [l for l in line_list if l.isDataLine()]
self.assertEqual(1, len(data_line_list))
data_line = data_line_list[0]
self.assertEqual(
data_line.column_id_list,
['resource_title', 'resource_reference', 'variation_category_item_list', 'inventory', 'quantity_unit', 'total_price'])
self.checkLineProperties(
data_line_list[0],
resource_title='product_A',
resource_reference='ref 2',
variation_category_item_list=[],
inventory=500,
quantity_unit='G',
# price for 1g is 3, we have 0.5Kg=500g
total_price=1500,
)
# listbox_total_price is an editable field using this for precision
self.assertEqual(self.portal.REQUEST.get('precision'), 2)
def testStockReport_valuation_and_quantity_price_in_different_unit(self):
# edit the product to set a default purchase price of 3300 per Kg, price
# for one gram will be 3.3
purchase_supply_line = self.portal.product_module.product_A.getDefaultPurchaseSupplyLineValue()
purchase_supply_line.edit(
quantity_unit_value=self.portal.portal_categories.quantity_unit.mass.kg,
base_price=3300
)
self._createConfirmedSalePackingListForStockReportTest()
request = self.portal.REQUEST
request.form['at_date'] = DateTime(2007, 3, 3)
request.form['node_category'] = 'site/demo_site_A'
request.form['simulation_period'] = 'future'
request.form['inventory_valuation_method'] = 'default_purchase_price'
line_list = self.portal.inventory_module.Base_viewStockReportBySite.listbox.\
get_value('default',
render_format='list', REQUEST=self.portal.REQUEST)
data_line_list = [l for l in line_list if l.isDataLine()]
self.assertEqual(1, len(data_line_list))
data_line = data_line_list[0]
self.assertEqual(
data_line.column_id_list,
['resource_title', 'resource_reference', 'variation_category_item_list', 'inventory', 'quantity_unit', 'total_price'])
self.checkLineProperties(
data_line_list[0],
resource_title='product_A',
resource_reference='ref 2',
variation_category_item_list=[],
inventory=1,
quantity_unit='G',
total_price=3.3,
)
# listbox_total_price is an editable field using this for precision
self.assertEqual(self.portal.REQUEST.get('precision'), 2)
def test_Folder_generateWorkflowReport(self):
# Create sales orders
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment