Commit 96e4c768 by Nicolas Wavrant

erp5_pdm: support calculation of base_price applying to all items of a same slice

1 parent d570fb1d
from math import log
result = context.getPriceParameterDict(context=movement, **kw)
# Calculate
# If slice_base_price:
# base_price = SUM(number_of_items_in_slice * slice_base_price) for each slice
# Then
# ((base_price + SUM(additional_price) +
# variable_value * SUM(variable_additional_price)) *
# (1 - MIN(1, MAX(SUM(discount_ratio) , exclusive_discount_ratio ))) +
......@@ -17,6 +22,23 @@ result = context.getPriceParameterDict(context=movement, **kw)
# depends on discrete variations, but also on a continuous property
# of the object
if result["slice_base_price"]:
total_price = 0.
quantity = movement.getQuantity()
sliced_base_price_list = zip(result["slice_base_price"], result["slice_quantity_range"])
for slice_price, slice_range in sliced_base_price_list:
slice_min, slice_max = slice_range
if slice_max is None:
slice_max = quantity + 1
if slice_min is None:
slice_min = 1
priced_quantity = min(slice_max - 1, quantity) - (slice_min - 1)
total_price += priced_quantity * slice_price
if result.get('base_unit_price', None) is None:
result["base_price"] = total_price / quantity
else:
result["base_price"] = round(total_price / quantity, int(round(- log(result['base_unit_price'], 10),0)))
base_price = result["base_price"]
if base_price in (None, ""):
# XXX Compatibility
......
......@@ -9,7 +9,9 @@
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
<list>
<string>default</string>
</list>
</value>
</item>
<item>
......@@ -70,6 +72,17 @@
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<list>
<string>base_price</string>
<string>base_unit_price</string>
<string>slice_base_price</string>
<string>slice_quantity_range</string>
</list>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_mapped_value_property_list</string> </value>
</item>
......
......@@ -9,7 +9,9 @@
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
<list>
<string>default</string>
</list>
</value>
</item>
<item>
......@@ -70,6 +72,17 @@
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<list>
<string>base_price</string>
<string>base_unit_price</string>
<string>slice_base_price</string>
<string>slice_quantity_range</string>
</list>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_mapped_value_property_list</string> </value>
</item>
......
......@@ -9,7 +9,9 @@
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
<list>
<string>default</string>
</list>
</value>
</item>
<item>
......@@ -70,6 +72,17 @@
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<list>
<string>base_price</string>
<string>base_unit_price</string>
<string>slice_base_price</string>
<string>slice_quantity_range</string>
</list>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_mapped_value_property_list</string> </value>
</item>
......
......@@ -106,6 +106,7 @@
<string>my_price_currency</string>
<string>my_start_date_range_min</string>
<string>my_start_date_range_max</string>
<string>my_base_price_per_slice</string>
</list>
</value>
</item>
......
......@@ -134,7 +134,9 @@
</item>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
......@@ -293,4 +295,17 @@
</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: [\'base_price\', \'base_unit_price\', \'slice_base_price\', \'slice_quantity_range\']</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>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_base_price_per_slice</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>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>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_checkbox</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Base Price Applies to Items in Slice</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="InteractionDefinition" module="Products.ERP5.Interaction"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>actbox_category</string> </key>
<value> <string>workflow</string> </value>
</item>
<item>
<key> <string>actbox_name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>actbox_url</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>activate_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>after_script_name</string> </key>
<value>
<list>
<string>SupplyLine_updateBasePricePerSlice</string>
</list>
</value>
</item>
<item>
<key> <string>before_commit_script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>guard</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SupplyLine_changeBasePricePerSlice</string> </value>
</item>
<item>
<key> <string>method_id</string> </key>
<value>
<list>
<string>_setBasePricePerSlice</string>
</list>
</value>
</item>
<item>
<key> <string>once_per_transaction</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>portal_type_filter</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>script_name</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>temporary_document_disallowed</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>trigger_type</string> </key>
<value> <int>2</int> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
supply_line = state_change['object']
price_parameter = 'base_price'
if supply_line.isBasePricePerSlice():
price_parameter = 'slice_base_price'
else:
price_parameter = 'base_price'
base_id = 'path'
supply_line.updateQuantityPredicate(price_parameter)
supply_line.updateCellRange(base_id=base_id)
supply_line = state_change['object']
to_delete_cell_list = [
cell for cell in supply_line.getCellIdList(base_id='path')
if hasattr(supply_line, cell)
]
supply_line.manage_delObjects(ids=to_delete_cell_list)
  • As discussed yesterday, this is incorrect:

    • it causes an inconsistency between existing cells and matrix.py-maintained internal cell index
    • it bypasses the work done by matrix.py to preserve cells uids

    The following code seems to work just as well without this deletion, and all that was needed was triggering another interaction workflow (which I'm not sure we should be having to begin with):

    supply_line = state_change['object']
    supply_line.updateQuantityPredicate(
      'slice_base_price'
      if supply_line.isBasePricePerSlice() else
      'base_price'
    )
    supply_line.updateCellRange(base_id='path')
    for cell_value in supply_line.getCellValueList(base_id='path'):
      # XXX: only to trigger supply_cell_interaction_workflow/scripts/SupplyCell_updateSliceBasePrice
      cell_value.setBasePrice(cell_value.getBasePrice())
    

    /cc @jerome @kazuhiko @seb

    Edited
  • I run the functionnal tests with your code, and they pass. You can commit it.

  • That XXX in my code is bad, so it cannot be used as such. More work is needed before it can be applied.

if state_change.kwargs['workflow_method_args'][0]:
price_parameter = 'slice_base_price'
else:
price_parameter = 'base_price'
supply_line.updateQuantityPredicate(price_parameter)
supply_line.updateCellRange(base_id='path')
<?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>state_change</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>SupplyLine_updateBasePricePerSlice</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -712,6 +712,8 @@ class Resource(XMLObject, XMLMatrix, VariatedMixin):
'non_discountable_additional_price': [],
'priced_quantity': None,
'base_unit_price': None,
'slice_base_price': [],
'slice_quantity_range': [],
}
if mapped_value is None:
return price_parameter_dict
......
......@@ -153,7 +153,7 @@ class SupplyLine(Path, Amount, XMLMatrix):
Return predicate id related to a price parameter.
"""
predicate_id_start_with = "quantity_range_"
if price_parameter != "base_price":
if price_parameter not in ("base_price", "slice_base_price"):
predicate_id_start_with = "%s_%s" % \
(price_parameter, predicate_id_start_with)
# XXX Hardcoded portal type name
......@@ -183,12 +183,12 @@ class SupplyLine(Path, Amount, XMLMatrix):
"""
# We need to keep compatibility with generated accessor
price_parameter = kw.get('price_parameter', "base_price")
if price_parameter == "base_price":
if price_parameter in ("base_price", "slice_base_price"):
method_name = "_baseGetQuantityStepList"
else:
method_name = 'get%sList' % \
convertToUpperCase("%s_quantity_step" % price_parameter)
return getattr(self, method_name)()
return getattr(self, method_name)() or []
security.declareProtected(Permissions.ModifyPortalContent,
'updateQuantityPredicate')
......@@ -208,7 +208,7 @@ class SupplyLine(Path, Amount, XMLMatrix):
# With this script, we can change the title of the predicate
script = getattr(self, 'SupplyLine_getTitle', None)
predicate_id_start_with = "quantity_range"
if price_parameter != "base_price":
if price_parameter not in ("base_price", "slice_base_price"):
predicate_id_start_with = "%s_%s" % \
(price_parameter, predicate_id_start_with)
for i in range(0, len(quantity_step_list)-1):
......@@ -218,7 +218,11 @@ class SupplyLine(Path, Amount, XMLMatrix):
p = self.newContent(id='%s_%s' % (predicate_id_start_with, str(i)),
portal_type='Predicate', int_index=i+1)
p.setCriterionPropertyList(('quantity', ))
p.setCriterion('quantity', min=min_quantity, max=max_quantity)
p.setCriterion(
'quantity',
min=min_quantity,
max=(None if price_parameter == 'slice_base_price' else max_quantity)
)
if script is not None:
title = script(min=min_quantity, max=max_quantity)
p.setTitle(title)
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!