From 438566357a9d4eecc2d9df88341e57f288f4b845 Mon Sep 17 00:00:00 2001 From: Sebastien Robin <seb@nexedi.com> Date: Fri, 7 Mar 2014 17:43:14 +0100 Subject: [PATCH] Order Builders: remove the whole testOrderBuilder passing So all newSimulation expected failures are removed. Make generateMovementListForStockOptimisation looking min_flow and max_delay on supply lines. Introduce a getNextAlertInventoryDate in addition to getNextNegativeInventoryDate. This allows to know when an inventory will be below a reference quantity. This is particularly helpful to know when an inventory is below the minimal admitted stock --- product/ERP5/Document/Resource.py | 16 +++++-- product/ERP5/Tool/SimulationTool.py | 50 ++++++++++++-------- product/ERP5/mixin/builder.py | 38 ++++++++++----- product/ERP5/tests/testOrderBuilder.py | 64 +++++++++++++------------- 4 files changed, 103 insertions(+), 65 deletions(-) diff --git a/product/ERP5/Document/Resource.py b/product/ERP5/Document/Resource.py index 61e4ff6783..0996a62c71 100644 --- a/product/ERP5/Document/Resource.py +++ b/product/ERP5/Document/Resource.py @@ -565,12 +565,22 @@ class Resource(XMLObject, XMLMatrix, VariatedMixin): 'getNextNegativeInventoryDate') def getNextNegativeInventoryDate(self, **kw): """ - Returns list of inventory grouped by section or site + Returns next date where the inventory will be negative + """ + return self.getNextAlertInventoryDate( + reference_quantity=0, **kw) + + security.declareProtected(Permissions.AccessContentsInformation, + 'getNextNegativeInventoryDate') + def getNextAlertInventoryDate(self, reference_quantity=0, **kw): + """ + Returns next date where the inventory will be below reference + quantity """ kw['resource_uid'] = self.getUid() portal_simulation = self.getPortalObject().portal_simulation - return portal_simulation.getNextNegativeInventoryDate(**kw) - + return portal_simulation.getNextAlertInventoryDate( + reference_quantity=reference_quantity, **kw) # Asset Price API security.declareProtected(Permissions.AccessContentsInformation, diff --git a/product/ERP5/Tool/SimulationTool.py b/product/ERP5/Tool/SimulationTool.py index a3d1b4340f..e96f82939b 100644 --- a/product/ERP5/Tool/SimulationTool.py +++ b/product/ERP5/Tool/SimulationTool.py @@ -2048,29 +2048,41 @@ class SimulationTool(BaseTool): assert len(inventory_list) == 1 return inventory_list[0] + security.declareProtected(Permissions.AccessContentsInformation, + 'getNextDeficientInventoryDate') + def getNextAlertInventoryDate(self, reference_quantity=0, src__=0, **kw): + """ + Give the next date where the quantity is lower than the + reference quantity. + """ + result = None + # First look at current inventory, we might have already an inventory + # lower than reference_quantity + current_inventory = self.getCurrentInventory(**kw) + if current_inventory < reference_quantity: + result = DateTime() + else: + result = self.getInventoryList(src__=src__, + sort_on = (('date', 'ascending'),), group_by_movement=1, **kw) + if src__ : + return result + total_inventory = 0. + for inventory in result: + if inventory['inventory'] is not None: + total_inventory += inventory['inventory'] + if total_inventory < reference_quantity: + result = inventory['date'] + break + return result + security.declareProtected(Permissions.AccessContentsInformation, 'getNextNegativeInventoryDate') - def getNextNegativeInventoryDate(self, src__=0, **kw): + def getNextNegativeInventoryDate(self, **kw): """ - Returns statistics of inventory grouped by section or site + Deficient Inventory with a reference_quantity of 0, so when the + stock will be negative """ - #sql_kw = self._generateSQLKeywordDict(order_by_expression='stock.date', **kw) - #sql_kw['group_by_expression'] = 'stock.uid' - #sql_kw['order_by_expression'] = 'stock.date' - - result = self.getInventoryList(src__=src__, - sort_on = (('date', 'ascending'),), group_by_movement=1, **kw) - if src__ : - return result - - total_inventory = 0. - for inventory in result: - if inventory['inventory'] is not None: - total_inventory += inventory['inventory'] - if total_inventory < 0: - return inventory['date'] - - return None + return self.getNextAlertInventoryDate(reference_quantity=0, **kw) ####################################################### # Traceability management diff --git a/product/ERP5/mixin/builder.py b/product/ERP5/mixin/builder.py index 0ef6fdfc9c..30be95ae81 100644 --- a/product/ERP5/mixin/builder.py +++ b/product/ERP5/mixin/builder.py @@ -183,6 +183,18 @@ class BuilderMixin(XMLObject, Amount, Predicate): group_by_section=0, **kw) id_count = 0 + # 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 + # like getPurchaseSupplyLineMinFlow. So we need to guess the supply prefix + supply_prefix = '' + delivery_type = self.getDeliveryPortalType() + portal = self.getPortalObject() + if delivery_type in portal.getPortalPurchaseTypeList(): + supply_prefix = 'purchase' + elif delivery_type in portal.getPortalSaleTypeList(): + supply_prefix = 'sale' + else: + supply_prefix = 'internal' for inventory_item in sql_list: if (inventory_item.inventory is not None): dumb_movement = inventory_item.getObject() @@ -191,27 +203,29 @@ class BuilderMixin(XMLObject, Amount, Predicate): str(id_count)) id_count += 1 resource_portal_type = self.getResourcePortalType() + resource = portal.portal_catalog.getObject(inventory_item.resource_uid) + assert resource.getPortalType() == resource_portal_type movement.edit( resource=inventory_item.resource_relative_url, variation_category_list=dumb_movement.getVariationCategoryList(), destination_value=self.getDestinationValue(), resource_portal_type=resource_portal_type, destination_section_value=self.getDestinationSectionValue()) - # We can do other test on inventory here - # XXX It is better if it can be sql parameters - #resource_portal_type = self.getResourcePortalType() - resource = movement.getResourceValue() - # FIXME: XXX Those properties are defined on a supply line !! - # min_flow, max_delay - min_flow = resource.getMinFlow(0) - assert resource.getPortalType() == resource_portal_type - if round(inventory_item.inventory, 5) < min_flow: - stop_date = resource.getNextNegativeInventoryDate( + # Get min_flow, max_delay on supply line + min_flow = 0 + max_delay = 0 + min_stock = 0 + if supply_prefix: + min_flow = resource.getProperty(supply_prefix + '_supply_line_min_flow', 0) + max_delay = resource.getProperty(supply_prefix + '_supply_line_max_delay', 0) + min_stock = resource.getProperty(supply_prefix + '_supply_line_min_stock', 0) + if round(inventory_item.inventory, 5) < min_stock: + stop_date = resource.getNextAlertInventoryDate( + reference_quantity=min_stock, variation_text=movement.getVariationText(), from_date=DateTime(), **kw) - if stop_date is None: - stop_date = DateTime() + assert stop_date is not None max_delay = resource.getMaxDelay(0) movement.edit( start_date=DateTime(((stop_date-max_delay).Date())), diff --git a/product/ERP5/tests/testOrderBuilder.py b/product/ERP5/tests/testOrderBuilder.py index 0246c2de1c..927b1a3867 100644 --- a/product/ERP5/tests/testOrderBuilder.py +++ b/product/ERP5/tests/testOrderBuilder.py @@ -55,8 +55,8 @@ class TestOrderBuilderMixin(TestOrderMixin): # defaults decrease_quantity = 1.0 - max_delay = 4.0 - min_flow = 7.0 + max_delay = 0.0 + min_flow = 0.0 def afterSetUp(self): """ @@ -76,7 +76,7 @@ class TestOrderBuilderMixin(TestOrderMixin): Sets min_flow on resource """ resource = sequence.get('resource') - resource.edit(min_flow=self.min_flow) + resource.edit(purchase_supply_line_min_flow=self.min_flow) def stepFillOrderBuilder(self, sequence): """ @@ -169,7 +169,7 @@ class TestOrderBuilderMixin(TestOrderMixin): # XXX: ... and for more lines/cells too order_line, = order.contentValues(portal_type=self.order_line_portal_type) self.assertEqual(order_line.getResourceValue(), resource) - self.assertEqual(order_line.getTotalQuantity(), self.min_flow) + self.assertEqual(order_line.getTotalQuantity(), self.wanted_quantity) def stepBuildOrderBuilder(self, sequence): """ @@ -204,7 +204,7 @@ class TestOrderBuilderMixin(TestOrderMixin): packing_list = packing_list_module.newContent( portal_type = self.packing_list_portal_type, source_value = organisation, - start_date = self.datetime, + start_date = self.datetime+10, specialise = self.business_process, ) @@ -214,6 +214,13 @@ class TestOrderBuilderMixin(TestOrderMixin): quantity = self.decrease_quantity, ) + self.decrease_quantity_matrix = { + 'variation/%s/blue' % resource.getRelativeUrl() : 1.0, + 'variation/%s/green' % resource.getRelativeUrl() : 2.0, + } + + self.wanted_quantity_matrix = self.decrease_quantity_matrix.copy() + packing_list_line.setVariationCategoryList( self.decrease_quantity_matrix.keys(), ) @@ -251,7 +258,7 @@ class TestOrderBuilderMixin(TestOrderMixin): packing_list = packing_list_module.newContent( portal_type = self.packing_list_portal_type, source_value = organisation, - start_date = self.datetime+14, + start_date = self.datetime+10, specialise = self.business_process, ) @@ -263,6 +270,23 @@ class TestOrderBuilderMixin(TestOrderMixin): packing_list.confirm() + def stepCreateVariatedResource(self, sequence=None, sequence_list=None, \ + **kw): + """ + Create a resource with variation + """ + portal = self.getPortal() + resource_module = portal.getDefaultModule(self.resource_portal_type) + resource = resource_module.newContent(portal_type=self.resource_portal_type) + resource.edit( + title = "VariatedResource%s" % resource.getId(), + variation_base_category_list = ['variation'], + ) + for color in ['blue', 'green']: + resource.newContent(portal_type='Product Individual Variation', + id=color, title=color) + sequence.edit(resource=resource) + class TestOrderBuilder(TestOrderBuilderMixin, ERP5TypeTestCase): """ Test Order Builder functionality @@ -301,14 +325,12 @@ class TestOrderBuilder(TestOrderBuilderMixin, ERP5TypeTestCase): str(self.datetime.earliestTime() + self.order_builder_hardcoded_time_diff)) - # We add 4 days to start date to reflect delays - self.wanted_stop_date = self.wanted_start_date + 4 + self.wanted_stop_date = self.wanted_start_date sequence_list = SequenceList() sequence_list.addSequenceString(self.common_sequence_string) sequence_list.play(self) - @newSimulationExpectedFailure def test_01a_simpleOrderBuilderVariatedResource(self, quiet=0, run=run_all_test): """ Test simple Order Builder for Variated Resource @@ -338,25 +360,17 @@ class TestOrderBuilder(TestOrderBuilderMixin, ERP5TypeTestCase): self.wanted_stop_date = self.wanted_start_date - self.decrease_quantity_matrix = { - 'size/Man' : 1.0, - 'size/Woman' : 2.0, - } - - self.wanted_quantity_matrix = self.decrease_quantity_matrix.copy() - sequence_list = SequenceList() sequence_list.addSequenceString(sequence_string) sequence_list.play(self) - @newSimulationExpectedFailure def test_02_maxDelayResourceOrderBuilder(self, quiet=0, run=run_all_test): """ Test max_delay impact on generated order start date """ if not run: return - self.max_delay = 14.0 + self.max_delay = 4.0 self.wanted_quantity = 1.0 self.wanted_start_date = DateTime( @@ -372,7 +386,6 @@ class TestOrderBuilder(TestOrderBuilderMixin, ERP5TypeTestCase): sequence_list.addSequenceString(self.common_sequence_string) sequence_list.play(self) - @newSimulationExpectedFailure def test_03_minFlowResourceOrderBuilder(self, quiet=0, run=run_all_test): """ Test min_flow impact on generated order line quantity @@ -390,20 +403,9 @@ class TestOrderBuilder(TestOrderBuilderMixin, ERP5TypeTestCase): sequence_list.addSequenceString(self.common_sequence_string) # case when min_flow > decreased_quantity - self.min_flow = 144.0 - self.wanted_quantity = self.min_flow + self.decrease_quantity - # why to order more than needed? - # self.wanted_quantity = self.min_flow - - sequence_list.play(self) - - # case when min_flow < decreased_quantity self.min_flow = 15.0 - self.decrease_quantity = 20.0 - self.wanted_quantity = self.min_flow + self.decrease_quantity - # why to order more than needed? - # self.wanted_quantity = self.decreased_quantity + self.wanted_quantity = self.min_flow sequence_list.play(self) -- 2.30.9