Commit 05f913a0 by Nicolas Wavrant

Price per quantity per slice

This MR introduces a second way of calculating the base price of a resource using "Base Price Quantity Step".

Currently, for a resource, we could define on a Supply Line "Quantity Steps",  and set a price for each of these steps. Then, depending on the quantity of the resource, the price of the unique matching step would apply.

For exemple : 

| Quantity Step Range | Price for the step|
|------------|---------|
| 0 -> 10 | 10€ |
| 11 -> 20 | 9€ |
| 21 -> inf | 8€ |

With the current method, for an Order of 15 products, the 2 range "11 -> 20" would apply, the unit price for the products would be 9€, and the total price would be 15*9 = 135€.

The new method of calculating would apply the _Price for the step_ __to, and only to, all items of this step__. Which means, for an order of 15 products, the 10 first products would have a unit price of 10€, and the 5 next products would have a unit price of 9€, which makes in the end a total price of 145€, and a base price of 9.66€.

/reviewed-on !896
2 parents db0ee9b5 334b21a7
Showing 80 changed files with 3469 additions and 94 deletions
......@@ -8,6 +8,10 @@
<workflow>edit_workflow, validation_workflow</workflow>
</chain>
<chain>
<type>Internal Supply Cell</type>
<workflow>supply_cell_interaction_workflow</workflow>
</chain>
<chain>
<type>Internal Supply Line</type>
<workflow>edit_workflow, reindex_object_interaction_workflow, supply_line_interaction_workflow</workflow>
</chain>
......@@ -24,6 +28,10 @@
<workflow>edit_workflow, validation_workflow</workflow>
</chain>
<chain>
<type>Purchase Supply Cell</type>
<workflow>supply_cell_interaction_workflow</workflow>
</chain>
<chain>
<type>Purchase Supply Line</type>
<workflow>edit_workflow, reindex_object_interaction_workflow, supply_line_interaction_workflow</workflow>
</chain>
......@@ -44,6 +52,10 @@
<workflow>edit_workflow, validation_workflow</workflow>
</chain>
<chain>
<type>Sale Supply Cell</type>
<workflow>supply_cell_interaction_workflow</workflow>
</chain>
<chain>
<type>Sale Supply Line</type>
<workflow>edit_workflow, reindex_object_interaction_workflow, supply_line_interaction_workflow</workflow>
</chain>
......@@ -52,6 +64,10 @@
<workflow>edit_workflow, validation_workflow</workflow>
</chain>
<chain>
<type>Supply Cell</type>
<workflow>supply_cell_interaction_workflow</workflow>
</chain>
<chain>
<type>Supply Line</type>
<workflow>edit_workflow, reindex_object_interaction_workflow, supply_line_interaction_workflow</workflow>
</chain>
......
......@@ -226,6 +226,7 @@
<string>my_max_order_quantity_variation_base_category_list</string>
<string>matrixbox_for_max_order_quantity</string>
<string>my_view_mode_target_delivery</string>
<string>my_base_price_per_slice</string>
</list>
</value>
</item>
......
<?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>
......@@ -11,7 +11,6 @@
<value>
<list>
<string>css_class</string>
<string>default</string>
<string>external_validator</string>
<string>title</string>
<string>width</string>
......@@ -57,10 +56,12 @@
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
<key> <string>css_class</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
......@@ -74,6 +75,14 @@
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>width</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
......@@ -86,15 +95,9 @@
<value> <string>figure</string> </value>
</item>
<item>
<key> <string>default</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>external_validator</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
......@@ -125,19 +128,6 @@
</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:[str(x) for x in here.getQuantityStepList()]</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="Method" module="Products.Formulator.MethodField"/>
</pickle>
<pickle>
......
......@@ -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>
......
......@@ -9,7 +9,9 @@
<item>
<key> <string>delegated_list</string> </key>
<value>
<list/>
<list>
<string>default</string>
</list>
</value>
</item>
<item>
......@@ -51,6 +53,12 @@
<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>
......@@ -70,6 +78,14 @@
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<list>
<string>base_price</string>
</list>
</value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_mapped_value_property_list</string> </value>
</item>
......@@ -87,4 +103,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/>
</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_base_price_per_slice</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewPDMFieldLibrary</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -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>
......
<?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>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_base_price_per_slice</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewPDMFieldLibrary</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
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
......
......@@ -116,6 +116,7 @@
<string>my_internal_supply_line_max_flow</string>
<string>my_internal_supply_line_min_stock</string>
<string>my_internal_supply_line_max_stock</string>
<string>my_internal_supply_line_base_price_per_slice</string>
</list>
</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>
......
<?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>my_internal_supply_line_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_base_price_per_slice</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewPDMFieldLibrary</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -116,6 +116,7 @@
<string>my_purchase_supply_line_max_flow</string>
<string>my_purchase_supply_line_min_stock</string>
<string>my_purchase_supply_line_max_stock</string>
<string>my_purchase_supply_line_base_price_per_slice</string>
</list>
</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>
......
<?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>my_purchase_supply_line_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_base_price_per_slice</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewPDMFieldLibrary</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -116,6 +116,7 @@
<string>my_sale_supply_line_max_flow</string>
<string>my_sale_supply_line_min_stock</string>
<string>my_sale_supply_line_max_stock</string>
<string>my_sale_supply_line_base_price_per_slice</string>
</list>
</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>
......
<?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