Commit 96e4c768 by Nicolas Wavrant

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

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 by Vincent Pelletier
  • 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.

Please register or sign in to reply
if state_change.kwargs['workflow_method_args'][0]:
  • Why not just supply_line.isBasePricePerSlice() ?

  • I did this because supply_line.isBasePricePerSlice() was returning the value before the property change. I think it happened when I tried to access base_price_per_slice in an interaction that was not triggered by _setBasePricePerSlice. It must have ended here during a refactoring. I'll fix to make the code more readable.

  • (In fact as you are going to commit your patch above, there is no need for me to commit this change now).

  • supply_line.isBasePricePerSlice() was returning the value before the property change.

    Why would it ? This is an after interaction. It has no reason to return the original value.

Please register or sign in to reply
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)
......
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 sign in to comment