Commit 15f08b29 authored by Sebastien Robin's avatar Sebastien Robin

simulation: review generated delivery builder (update existing deliveries, date precision)

Up to know, order builders were mostly used having in mind the idea of
deleting all previously created automated orders.

This way was mostly requesting to run generated delivery builder only
once per night, assuming there is no users working at that time.

So to allows running very often (many times a day) generated delivery
builders, allow them to update existing deliveries. This way :
- automated deliveries could be much more up to date
- there is no need to delete everything
- this generate few activities, there is no need of a long calculation
  to update everything

Also, the stock optimization was using dates with a precision of one
full day. Generic code must support much better precision.
parent 71abefe6
...@@ -90,3 +90,20 @@ class GeneratedDeliveryBuilder(BuilderMixin): ...@@ -90,3 +90,20 @@ class GeneratedDeliveryBuilder(BuilderMixin):
, PropertySheet.Comment , PropertySheet.Comment
, PropertySheet.DeliveryBuilder , PropertySheet.DeliveryBuilder
) )
def _setDeliveryMovementProperties(self, delivery_movement,
simulation_movement, property_dict,
update_existing_movement=0,
force_update=0, activate_kw=None):
"""
Initialize or update delivery movement properties.
"""
if getattr(simulation_movement, 'getMappedProperty', None) is not None:
property_dict['quantity'] = simulation_movement.getMappedProperty('quantity')
else:
property_dict['quantity'] = simulation_movement.getQuantity()
property_dict['price'] = simulation_movement.getPrice()
if update_existing_movement:
property_dict['quantity'] = (delivery_movement.getQuantity() or 0.0) + property_dict['quantity']
# Update properties on object (quantity, price...)
delivery_movement._edit(force_update=1, **property_dict)
...@@ -182,7 +182,6 @@ class BuilderMixin(XMLObject, Amount, Predicate): ...@@ -182,7 +182,6 @@ class BuilderMixin(XMLObject, Amount, Predicate):
group_by_node=1, group_by_node=1,
group_by_section=0, group_by_section=0,
**kw) **kw)
id_count = 0
# min_flow and max_delay are stored on a supply line. By default # min_flow and max_delay are stored on a supply line. By default
# we can get them through a method having the right supply type prefix # we can get them through a method having the right supply type prefix
# like getPurchaseSupplyLineMinFlow. So we need to guess the supply prefix # like getPurchaseSupplyLineMinFlow. So we need to guess the supply prefix
...@@ -195,25 +194,30 @@ class BuilderMixin(XMLObject, Amount, Predicate): ...@@ -195,25 +194,30 @@ class BuilderMixin(XMLObject, Amount, Predicate):
supply_prefix = 'sale' supply_prefix = 'sale'
else: else:
supply_prefix = 'internal' supply_prefix = 'internal'
for inventory_item in sql_list:
if (inventory_item.inventory is not None):
dumb_movement = inventory_item.getObject()
# Create temporary movement
movement = newTempMovement(self.getPortalObject(),
str(id_count))
id_count += 1
resource_portal_type_list = self.getResourcePortalTypeList() resource_portal_type_list = self.getResourcePortalTypeList()
resource = portal.portal_catalog.getObject(inventory_item.resource_uid) def newMovement(inventory_item, resource):
# Create temporary movement
movement = newTempMovement(self.getPortalObject(), "temp")
dumb_movement = inventory_item.getObject()
resource_portal_type = resource.getPortalType() resource_portal_type = resource.getPortalType()
assert resource_portal_type in (resource_portal_type_list), \ assert resource_portal_type in (resource_portal_type_list), \
"Builder %r does not support resource of type : %r" % ( "Builder %r does not support resource of type : %r" % (
self.getRelativeUrl(), resource_portal_type) self.getRelativeUrl(), resource_portal_type)
movement.edit( movement.edit(
resource=inventory_item.resource_relative_url, resource=inventory_item.resource_relative_url,
# XXX FIXME define on a supply line
# quantity_unit
quantity_unit=resource.getQuantityUnit(),
variation_category_list=dumb_movement.getVariationCategoryList(), variation_category_list=dumb_movement.getVariationCategoryList(),
destination_value=self.getDestinationValue(), destination_value=self.getDestinationValue(),
resource_portal_type=resource_portal_type, resource_portal_type=resource_portal_type,
destination_section_value=self.getDestinationSectionValue()) destination_section_value=self.getDestinationSectionValue())
return movement
for inventory_item in sql_list:
if (inventory_item.inventory is not None):
resource = portal.portal_catalog.getObject(inventory_item.resource_uid)
# Get min_flow, max_delay on supply line # Get min_flow, max_delay on supply line
min_flow = 0 min_flow = 0
max_delay = 0 max_delay = 0
...@@ -225,20 +229,38 @@ class BuilderMixin(XMLObject, Amount, Predicate): ...@@ -225,20 +229,38 @@ class BuilderMixin(XMLObject, Amount, Predicate):
if round(inventory_item.inventory, 5) < min_stock: if round(inventory_item.inventory, 5) < min_stock:
stop_date = resource.getNextAlertInventoryDate( stop_date = resource.getNextAlertInventoryDate(
reference_quantity=min_stock, reference_quantity=min_stock,
variation_text=movement.getVariationText(), variation_text=inventory_item.variation_text,
from_date=DateTime(), from_date=DateTime(),
**kw) **kw)
if stop_date != None: if stop_date != None:
movement = newMovement(inventory_item, resource)
max_delay = resource.getMaxDelay(0) max_delay = resource.getMaxDelay(0)
movement.edit( movement.edit(
start_date=DateTime(((stop_date-max_delay).Date())), start_date=stop_date-max_delay,
stop_date=DateTime(stop_date.Date()), stop_date=stop_date,
quantity=max(min_flow, -inventory_item.inventory), quantity=max(min_flow, -inventory_item.inventory),
quantity_unit=resource.getQuantityUnit()
# XXX FIXME define on a supply line
# quantity_unit
) )
movement_list.append(movement) movement_list.append(movement)
# We could need to cancel automated stock optimization if for some reasons
# previous optimisations are obsolete
elif round(inventory_item.inventory, 5) > min_stock:
delta = inventory_item.inventory - min_stock
optimized_inventory_list = portal.portal_simulation.getInventoryList(
resource_uid=inventory_item.resource_uid,
node_uid=inventory_item.node_uid,
variation_text=inventory_item.variation_text,
simulation_state="auto_planned",
sort_on=[("date", "descending")],
)
for optimized_inventory in optimized_inventory_list:
movement = newMovement(inventory_item, resource)
quantity = min(delta, optimized_inventory.inventory)
delta = delta - quantity
movement.edit(start_date=optimized_inventory.date,
quantity=-quantity)
movement_list.append(movement)
if delta <= 0:
break
return movement_list return movement_list
def _searchMovementList(self, **kw): def _searchMovementList(self, **kw):
......
...@@ -64,6 +64,10 @@ class TestOrderBuilderMixin(TestOrderMixin): ...@@ -64,6 +64,10 @@ class TestOrderBuilderMixin(TestOrderMixin):
self.createCategories() self.createCategories()
self.validateRules() self.validateRules()
def assertDateAlmostEquals(self, first_date, second_date):
self.assertTrue(abs(first_date - second_date) < 1.0/86400,
"%r != %r" % (first_date, second_date))
def stepSetMaxDelayOnResource(self, sequence): def stepSetMaxDelayOnResource(self, sequence):
""" """
Sets max_delay on resource Sets max_delay on resource
...@@ -137,8 +141,8 @@ class TestOrderBuilderMixin(TestOrderMixin): ...@@ -137,8 +141,8 @@ class TestOrderBuilderMixin(TestOrderMixin):
# XXX: add support for more generated documents # XXX: add support for more generated documents
order, = sequence.get('generated_document_list') order, = sequence.get('generated_document_list')
self.assertEqual(order.getDestinationValue(), organisation) self.assertEqual(order.getDestinationValue(), organisation)
self.assertEqual(order.getStartDate(), self.wanted_start_date) self.assertDateAlmostEquals(order.getStartDate(), self.wanted_start_date)
self.assertEqual(order.getStopDate(), self.wanted_stop_date) self.assertDateAlmostEquals(order.getStopDate(), self.wanted_stop_date)
# XXX: ... and for more lines/cells too # XXX: ... and for more lines/cells too
order_line, = order.contentValues(portal_type=self.order_line_portal_type) order_line, = order.contentValues(portal_type=self.order_line_portal_type)
...@@ -163,8 +167,8 @@ class TestOrderBuilderMixin(TestOrderMixin): ...@@ -163,8 +167,8 @@ class TestOrderBuilderMixin(TestOrderMixin):
# XXX: add support for more generated documents # XXX: add support for more generated documents
order, = sequence.get('generated_document_list') order, = sequence.get('generated_document_list')
self.assertEqual(order.getDestinationValue(), organisation) self.assertEqual(order.getDestinationValue(), organisation)
self.assertEqual(order.getStartDate(), self.wanted_start_date) self.assertDateAlmostEquals(self.wanted_start_date, order.getStartDate())
self.assertEqual(order.getStopDate(), self.wanted_stop_date) self.assertDateAlmostEquals(self.wanted_stop_date, order.getStopDate())
# XXX: ... and for more lines/cells too # XXX: ... and for more lines/cells too
order_line, = order.contentValues(portal_type=self.order_line_portal_type) order_line, = order.contentValues(portal_type=self.order_line_portal_type)
...@@ -322,8 +326,7 @@ class TestOrderBuilder(TestOrderBuilderMixin, ERP5TypeTestCase): ...@@ -322,8 +326,7 @@ class TestOrderBuilder(TestOrderBuilderMixin, ERP5TypeTestCase):
self.wanted_quantity = 1.0 self.wanted_quantity = 1.0
self.wanted_start_date = DateTime( self.wanted_start_date = DateTime(
str(self.datetime.earliestTime() str(self.datetime + self.order_builder_hardcoded_time_diff))
+ self.order_builder_hardcoded_time_diff))
self.wanted_stop_date = self.wanted_start_date self.wanted_stop_date = self.wanted_start_date
...@@ -355,7 +358,7 @@ class TestOrderBuilder(TestOrderBuilderMixin, ERP5TypeTestCase): ...@@ -355,7 +358,7 @@ class TestOrderBuilder(TestOrderBuilderMixin, ERP5TypeTestCase):
self.wanted_quantity = 1.0 self.wanted_quantity = 1.0
self.wanted_start_date = DateTime( self.wanted_start_date = DateTime(
str(self.datetime.earliestTime() + str(self.datetime +
self.order_builder_hardcoded_time_diff)) self.order_builder_hardcoded_time_diff))
self.wanted_stop_date = self.wanted_start_date self.wanted_stop_date = self.wanted_start_date
...@@ -374,13 +377,11 @@ class TestOrderBuilder(TestOrderBuilderMixin, ERP5TypeTestCase): ...@@ -374,13 +377,11 @@ class TestOrderBuilder(TestOrderBuilderMixin, ERP5TypeTestCase):
self.wanted_quantity = 1.0 self.wanted_quantity = 1.0
self.wanted_start_date = DateTime( self.wanted_start_date = DateTime(
str(self.datetime.earliestTime() str(self.datetime - self.max_delay
- self.max_delay
+ self.order_builder_hardcoded_time_diff)) + self.order_builder_hardcoded_time_diff))
self.wanted_stop_date = DateTime( self.wanted_stop_date = DateTime(
str(self.datetime.earliestTime() str(self.datetime + self.order_builder_hardcoded_time_diff))
+ self.order_builder_hardcoded_time_diff))
sequence_list = SequenceList() sequence_list = SequenceList()
sequence_list.addSequenceString(self.common_sequence_string) sequence_list.addSequenceString(self.common_sequence_string)
...@@ -394,8 +395,7 @@ class TestOrderBuilder(TestOrderBuilderMixin, ERP5TypeTestCase): ...@@ -394,8 +395,7 @@ class TestOrderBuilder(TestOrderBuilderMixin, ERP5TypeTestCase):
self.wanted_quantity = 1.0 self.wanted_quantity = 1.0
self.wanted_start_date = DateTime( self.wanted_start_date = DateTime(
str(self.datetime.earliestTime() str(self.datetime + self.order_builder_hardcoded_time_diff))
+ self.order_builder_hardcoded_time_diff))
self.wanted_stop_date = self.wanted_start_date self.wanted_stop_date = self.wanted_start_date
......
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