diff --git a/product/ERP5/Document/Inventory.py b/product/ERP5/Document/Inventory.py index 1586b864ccc3c887857e813a39294c9743619fbb..c7015459af75889e1b6d64d31c14da4a2ec8a2c0 100644 --- a/product/ERP5/Document/Inventory.py +++ b/product/ERP5/Document/Inventory.py @@ -198,6 +198,13 @@ class Inventory(Delivery): inventory_id = self.getId() list_method = inventory_calculation_dict['list_method'] method = getattr(self, list_method) + + __order_id_counter_list = [0] + def getOrderIdCounter(): + value = __order_id_counter_list[0] + __order_id_counter_list[0] = value + 1 + return value + for movement in method(): if movement.getResourceValue() is not None and \ movement.getInventoriatedQuantity() not in (None, ''): @@ -240,7 +247,10 @@ class Inventory(Delivery): # Create tmp movement kwd = {'uid': movement.getUid(), - 'start_date': stop_date} + 'start_date': stop_date, + 'order_id': getOrderIdCounter(), + 'mirror_order_id':getOrderIdCounter() + } temp_delivery_line = temp_constructor(self, inventory_id) @@ -284,7 +294,10 @@ class Inventory(Delivery): diff_quantity = - inventory_value[tuple(second_level_key)] kwd = {'uid': inventory_uid, - 'start_date': stop_date} + 'start_date': stop_date, + 'order_id': getOrderIdCounter(), + 'mirror_order_id':getOrderIdCounter() + } # create the tmp line and set category on it temp_delivery_line = temp_constructor(self, @@ -320,8 +333,15 @@ class Inventory(Delivery): immediate_reindex_archive=immediate_reindex_archive) if stock_object_list: + # Delete existing records first. + self.portal_catalog.catalogObjectList( + stock_object_list[:], method_id_list=('z0_uncatalog_stock', ), + sql_catalog_id = sql_catalog_id, + disable_cache=1, check_uid=0, disable_archive=disable_archive, + immediate_reindex_archive=immediate_reindex_archive) + # Then insert new records without delete. self.portal_catalog.catalogObjectList( - stock_object_list, method_id_list=('z_catalog_stock_list', ), + stock_object_list[:], method_id_list=('z_catalog_stock_list_without_delete_for_inventory_virtual_movement', ), sql_catalog_id = sql_catalog_id, disable_cache=1, check_uid=0, disable_archive=disable_archive, immediate_reindex_archive=immediate_reindex_archive) diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_uncatalog_stock.xml b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_uncatalog_stock.xml index 1bff1076df06fe2a8032e947bd4472b342811185..a54c4725188d1b9329f8c7bbcddfbcaa43bedf0f 100644 --- a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_uncatalog_stock.xml +++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z0_uncatalog_stock.xml @@ -54,7 +54,7 @@ <key> <string>src</string> </key> <value> <string encoding="cdata"><![CDATA[ -DELETE FROM stock WHERE <dtml-sqltest uid op=eq type=int> +DELETE FROM stock WHERE <dtml-sqltest uid op=eq type=int multiple> ]]></string> </value> </item> diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_stock_list_without_delete_for_inventory_virtual_movement.catalog_keys.xml b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_stock_list_without_delete_for_inventory_virtual_movement.catalog_keys.xml new file mode 100644 index 0000000000000000000000000000000000000000..a540f9431e9c394244ed71562b197938e6fe9258 --- /dev/null +++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_stock_list_without_delete_for_inventory_virtual_movement.catalog_keys.xml @@ -0,0 +1,2 @@ +<catalog_method> +</catalog_method> diff --git a/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_stock_list_without_delete_for_inventory_virtual_movement.xml b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_stock_list_without_delete_for_inventory_virtual_movement.xml new file mode 100644 index 0000000000000000000000000000000000000000..160c9ffe95ef67d5fd7a21696c978c7245a83d0b --- /dev/null +++ b/product/ERP5/bootstrap/erp5_mysql_innodb_catalog/CatalogMethodTemplateItem/portal_catalog/erp5_mysql_innodb/z_catalog_stock_list_without_delete_for_inventory_virtual_movement.xml @@ -0,0 +1,158 @@ +<?xml version="1.0"?> +<ZopeData> + <record id="1" aka="AAAAAAAAAAE="> + <pickle> + <global name="SQL" module="Products.ZSQLMethods.SQL"/> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>arguments_src</string> </key> + <value> <string>uid\r\n +order_id\r\n +mirror_order_id\r\n +getExplanationUid\r\n +getResourceUid\r\n +getInventoriatedQuantity\r\n +getSourceUid\r\n +getDestinationUid\r\n +getSourceSectionUid\r\n +getDestinationSectionUid\r\n +isMovement\r\n +isCancellationAmount\r\n +isInventoryMovement\r\n +getSourcePaymentUid\r\n +getDestinationPaymentUid\r\n +getSourceFunctionUid\r\n +getDestinationFunctionUid\r\n +getSourceProjectUid\r\n +getDestinationProjectUid\r\n +getSourceFundingUid\r\n +getDestinationFundingUid\r\n +getSourcePaymentRequestUid\r\n +getDestinationPaymentRequestUid\r\n +getSimulationState\r\n +getSourceInventoriatedTotalAssetPrice\r\n +getDestinationInventoriatedTotalAssetPrice\r\n +getStartDate\r\n +getStopDate\r\n +isAccountable\r\n +getPortalType\r\n +getVariationText\r\n +getSubVariationText</string> </value> + </item> + <item> + <key> <string>connection_id</string> </key> + <value> <string>erp5_sql_connection</string> </value> + </item> + <item> + <key> <string>id</string> </key> + <value> <string>z_catalog_stock_list_without_delete_for_inventory_virtual_movement</string> </value> + </item> + <item> + <key> <string>src</string> </key> + <value> <string encoding="cdata"><![CDATA[ + +<dtml-let row_list="[]">\n + <dtml-in prefix="loop" expr="_.range(_.len(uid))">\n + <dtml-if "not(isInventoryMovement[loop_item]) and isMovement[loop_item] and getResourceUid[loop_item]">\n + <dtml-if "getDestinationUid[loop_item]">\n + <dtml-call expr="row_list.append([\n + uid[loop_item], \n + order_id[loop_item],\n + getExplanationUid[loop_item],\n + getDestinationUid[loop_item],\n + getDestinationSectionUid[loop_item],\n + getDestinationPaymentUid[loop_item],\n + getDestinationFunctionUid[loop_item],\n + getDestinationProjectUid[loop_item], \n + getDestinationFundingUid[loop_item], \n + getDestinationPaymentRequestUid[loop_item], \n + getSourceSectionUid[loop_item], \n + getSourceUid[loop_item], \n + getResourceUid[loop_item],\n + getInventoriatedQuantity[loop_item],\n + isCancellationAmount[loop_item],\n + isAccountable[loop_item],\n + getStopDate[loop_item], \n + getStartDate[loop_item], \n + getDestinationInventoriatedTotalAssetPrice[loop_item], \n + getPortalType[loop_item], \n + getSimulationState[loop_item], \n + getVariationText[loop_item],\n + getSubVariationText[loop_item]])">\n + </dtml-if>\n + <dtml-if "getSourceUid[loop_item]">\n + <dtml-call expr="row_list.append([\n + uid[loop_item], \n + mirror_order_id[loop_item],\n + getExplanationUid[loop_item],\n + getSourceUid[loop_item],\n + getSourceSectionUid[loop_item],\n + getSourcePaymentUid[loop_item],\n + getSourceFunctionUid[loop_item],\n + getSourceProjectUid[loop_item], \n + getSourceFundingUid[loop_item], \n + getSourcePaymentRequestUid[loop_item], \n + getDestinationSectionUid[loop_item], \n + getDestinationUid[loop_item], \n + getResourceUid[loop_item],\n + -(getInventoriatedQuantity[loop_item] or 0), \n + isCancellationAmount[loop_item],\n + isAccountable[loop_item],\n + getStartDate[loop_item], \n + getStopDate[loop_item],\n + getSourceInventoriatedTotalAssetPrice[loop_item], \n + getPortalType[loop_item], \n + getSimulationState[loop_item], \n + getVariationText[loop_item],\n + getSubVariationText[loop_item]])">\n + </dtml-if>\n + </dtml-if>\n + </dtml-in> \n + <dtml-if "row_list">\n +INSERT INTO\n + stock\n +VALUES\n + <dtml-in prefix="row" expr="row_list">\n +(\n + <dtml-sqlvar expr="row_item[0]" type="int">,\n + <dtml-sqlvar expr="row_item[1]" type="int">,\n + <dtml-sqlvar expr="row_item[2]" type="int" optional>,\n + <dtml-sqlvar expr="row_item[3]" type="int">,\n + <dtml-sqlvar expr="row_item[4]" type="int" optional>, \n + <dtml-sqlvar expr="row_item[5]" type="int" optional>, \n + <dtml-sqlvar expr="row_item[6]" type="int" optional>,\n + <dtml-sqlvar expr="row_item[7]" type="int" optional>,\n + <dtml-sqlvar expr="row_item[8]" type="int" optional>,\n + <dtml-sqlvar expr="row_item[9]" type="int" optional>,\n + <dtml-sqlvar expr="row_item[10]" type="int" optional>,\n + <dtml-sqlvar expr="row_item[11]" type="int" optional>,\n + <dtml-sqlvar expr="row_item[12]" type="int">, \n + <dtml-sqlvar expr="row_item[13]" type="float" optional>,\n + <dtml-sqlvar expr="row_item[14]" type="int">, \n + <dtml-sqlvar expr="row_item[15]" type="int">,\n + <dtml-sqlvar expr="row_item[16]" type="datetime" optional>,\n + <dtml-sqlvar expr="row_item[17]" type="datetime" optional>,\n + <dtml-sqlvar expr="row_item[18]" type="float" optional>,\n + <dtml-sqlvar expr="row_item[19]" type="string" optional>,\n + <dtml-sqlvar expr="row_item[20]" type="string" optional>,\n + <dtml-sqlvar expr="row_item[21]" type="string" optional>,\n + <dtml-sqlvar expr="row_item[22]" type="string" optional>\n +)\n +<dtml-if sequence-end><dtml-else>,</dtml-if>\n + </dtml-in>\n + </dtml-if>\n +</dtml-let>\n + + +]]></string> </value> + </item> + <item> + <key> <string>title</string> </key> + <value> <string></string> </value> + </item> + </dictionary> + </pickle> + </record> +</ZopeData> diff --git a/product/ERP5/tests/testInventory.py b/product/ERP5/tests/testInventory.py index 3b758c0a2ec213bd251f7ebe7666c10cbaf0f4ee..17318414f2e1f130a3698c2b1e1895ef2ccb0554 100644 --- a/product/ERP5/tests/testInventory.py +++ b/product/ERP5/tests/testInventory.py @@ -2078,56 +2078,6 @@ class TestInventory(TestOrderMixin, ERP5TypeTestCase): inventory_list.append(inventory) sequence.edit(inventory_list=inventory_list) - def stepCreateTwoResourceFullInventoryAtTheDate(self, sequence=None, - sequence_list=None, **kw): - """ Create Full Inventory at the date' """ - inventory_list = sequence.get('inventory_list',[]) - if kw.get('start_date', None) is not None: - start_date = kw['start_date'] - else: - start_date = '2013/03/12 00:00:00 GMT+9' - if kw.get('inventory1', None) is not None: - inventory1 = kw['inventory1'] - else: - inventory_1 = 10 - if kw.get('inventory2', None) is not None: - inventory2 = kw['inventory2'] - else: - inventory2 = 100 - - inventory = self.createInventory(sequence=sequence) - inventory_list = sequence.get('inventory_list',[]) - inventory.edit(full_inventory=True, - start_date=start_date) - inventory_line = inventory.newContent( - portal_type = self.inventory_line_portal_type, - resource_value = sequence.get("resource"), - inventory = inventory1) - inventory_line = inventory.newContent( - portal_type = self.inventory_line_portal_type, - resource_value = sequence.get("second_resource"), - inventory = inventory2) - inventory.deliver() - inventory_list.append(inventory) - sequence.edit(inventory_list=inventory_list) - - def stepCreateTwoResourceFullInventoryAtTheDate1(self, sequence=None, - sequence_list=None, **kw): - params = dict(start_date=self.two_resource_full_inventory1_start_date, - inventory1=self.two_resource_full_inventory1_inventory_1, - inventory2=self.two_resource_full_inventory1_inventory_2) - self.stepCreateTwoResourceFullInventoryAtTheDate(sequence, sequence_list, - **params) - - def stepCreateTwoResourceFullInventoryAtTheDate2(self, sequence=None, - sequence_list=None, **kw): - params = dict(start_date=self.two_resource_full_inventory2_start_date, - inventory1=self.two_resource_full_inventory2_inventory_1, - inventory2=self.two_resource_full_inventory2_inventory_2) - self.stepCreateTwoResourceFullInventoryAtTheDate(sequence, sequence_list, - **params) - - def stepTestFullInventoryWithResourceCategory(self, sequence=None, sequence_list=None, @@ -2580,20 +2530,6 @@ class TestInventory(TestOrderMixin, ERP5TypeTestCase): node_uid=node_value.getUid(), resource_uid=resource_value.getUid()) - def stepCheckFullInventoryUpdateWithValidDateOrder( - self, sequence=None, sequence_list=None, **kw): - resource_value = sequence.get('resource') - second_resource_value = sequence.get('second_resource') - node_value = sequence.get('node') - section_value = sequence.get('section') - self._testGetInventory(expected=100, - section_uid=section_value.getUid(), - node_uid=node_value.getUid(), - resource_uid=resource_value.getUid()) - self._testGetInventory(expected=0, - section_uid=section_value.getUid(), - node_uid=node_value.getUid(), - resource_uid=second_resource_value.getUid()) def test_01_getInventory(self, quiet=0, run=run_all_test): """ @@ -3261,46 +3197,69 @@ class TestInventory(TestOrderMixin, ERP5TypeTestCase): sequence_list.addSequenceString(sequence_string) sequence_list.play(self) - def test_15_FullInventoryUpdateWithValidDateOrder( - self, quiet=0, run=run_all_test): - """ - Confirm Full inventory update with a valid start_date order + def test_15_FullInventoryCanCreatesManyVirtualCompensationMovement(self, quiet=0, run=run_all_test): + organisation = self.portal.organisation_module.newContent(portal_type='Organisation') + resource_value_list = [] + for i in range(2000): + resource_value_list.append(self.portal.product_module.newContent(portal_type='Product')) - The case is: - 1) full inventory: 2013/02/01,section=A, node=B, resource=X, quantity=15 - resource=Y, quantity=20 - 2) full inventory: 2013/02/02,section=A, node=B, resource=X, quantity=20 - resource=Y, quantity=50 - 3) full inventory: 2013/02/10,section=A, node=B, resource=X, quantity=100 - -> X:100 - Y:0 # creates a dummy movement with quantity=-50 - [test] - getInventory(resource=X, to_date=2013/02/15) should return 100 - getInventory(resource=Y, to_date=2013/02/15) should return 0 - """ - if not run: return + self.commit() + self.tic() - self.two_resource_full_inventory1_start_date = '2013/02/01 00:00:00 GMT+9' - self.two_resource_full_inventory1_inventory_1 = 15 - self.two_resource_full_inventory1_inventory_2 = 20 - self.two_resource_full_inventory2_start_date = '2013/02/02 00:00:00 GMT+9' - self.two_resource_full_inventory2_inventory_1 = 20 - self.two_resource_full_inventory2_inventory_2 = 50 - self.full_inventory_start_date_1 = '2013/02/10 00:00:00 GMT+9' - sequence_list = SequenceList() - sequence_string = 'CreateOrganisationsForModule \ - CreateNotVariatedResource \ - CreateNotVariatedSecondResource \ - CreateTwoResourceFullInventoryAtTheDate1 \ - Tic \ - CreateTwoResourceFullInventoryAtTheDate2 \ - Tic \ - CreateFullInventoryAtTheDate1 \ - Tic \ - CheckFullInventoryUpdateWithValidDateOrder \ - ' - sequence_list.addSequenceString(sequence_string) - sequence_list.play(self) + # Create initial inventory + date_1 = DateTime('2013/04/29 00:00:00 GMT+9') + result = self.portal.portal_simulation.getCurrentInventoryList(at_date=date_1, + section_uid=organisation.getUid(), + node_uid=organisation.getUid(), + group_by_resource=1) + self.assertEqual(len(result), 0) + + full_inventory_1 = self.portal.inventory_module.newContent(portal_type='Inventory') + full_inventory_1.edit(destination_section_value=organisation, + destination_value=organisation, + start_date=date_1, + full_inventory=True) + for resource_value in resource_value_list: + full_inventory_1.newContent(portal_type='Inventory Line', + resource_value=resource_value, + quantity=123) + full_inventory_1.deliver() + + self.commit() + self.tic() + + result = self.portal.portal_simulation.getCurrentInventoryList(at_date=date_1, + section_uid=organisation.getUid(), + node_uid=organisation.getUid(), + group_by_resource=1) + self.assertEqual(sorted([(brain.resource_uid, brain.inventory) + for brain in result]), + sorted([(movement.getResourceUid(), movement.getQuantity()) + for movement in full_inventory_1.getMovementList()])) + + # Create second inventory which deletes inventories of many resources. + date_2 = DateTime('2013/05/03 00:00:00 GMT+9') + full_inventory_2 = self.portal.inventory_module.newContent(portal_type='Inventory') + full_inventory_2.edit(destination_section_value=organisation, + destination_value=organisation, + start_date=date_2, + full_inventory=True) + full_inventory_2.newContent(portal_type='Inventory Line', + resource_value=resource_value_list[0], + quantity=1) + full_inventory_2.deliver() + + self.commit() + self.tic() + + result = self.portal.portal_simulation.getCurrentInventoryList(at_date=date_2, + section_uid=organisation.getUid(), + node_uid=organisation.getUid(), + group_by_resource=1) + self.assertEqual(sorted([(brain.resource_uid, brain.inventory) + for brain in result if brain.inventory != 0]), + sorted([(movement.getResourceUid(), movement.getQuantity()) + for movement in full_inventory_2.getMovementList()])) def test_suite(): suite = unittest.TestSuite()