Commit 079b8a74 authored by Xiaowu Zhang's avatar Xiaowu Zhang

fix asset price on accounting line

See merge request nexedi/erp5!1611
parents f8d826d4 b7f87dac
Pipeline #21229 failed with stage
in 0 seconds
...@@ -9,21 +9,9 @@ What is expected with this script: ...@@ -9,21 +9,9 @@ What is expected with this script:
- In reality we probably also want that amount on vat line match invoice vat - In reality we probably also want that amount on vat line match invoice vat
amount, but we have ignored this. amount, but we have ignored this.
""" """
precision = context.getQuantityPrecisionFromResource(context.getResource())
resource = context.getResourceValue()
line = None
total_quantity = 0.0
line_list = context.getMovementList( line_list = context.getMovementList(
portal_type=context.getPortalAccountingMovementTypeList()) portal_type=context.getPortalAccountingMovementTypeList())
for line in line_list:
line_quantity = round(line.getQuantity(), precision)
line.setQuantity(line_quantity)
total_quantity += line_quantity
# If no "line" found (eg no SIT line), then do nothing. This is in the case where a SIT # If no "line" found (eg no SIT line), then do nothing. This is in the case where a SIT
# has only Invoice Line and no SIT Line. Otherwise account_type_dict will be empty => # has only Invoice Line and no SIT Line. Otherwise account_type_dict will be empty =>
# asset_line = None => the assert below will fail because getTotalPrice() will returns the # asset_line = None => the assert below will fail because getTotalPrice() will returns the
...@@ -31,28 +19,26 @@ for line in line_list: ...@@ -31,28 +19,26 @@ for line in line_list:
if not line_list: if not line_list:
return return
abs_total_quantity = abs(round(total_quantity, precision))
# The total quantity should be zero with a little error, if simulation has been
# completely applied, because the debit and the credit must be balanced. However,
# this is not the case, if the delivery is divergent, as the builder does not
# adopt prevision automatically, when a conflict happens between the simulation
# and user-entered values.
if abs_total_quantity > 2 * resource.getBaseUnitQuantity():
return
total_price = round(context.getTotalPrice(), precision)
account_type_dict = {} account_type_dict = {}
source_exchange_ratio = None
destination_exchange_ratio = None
for line in line_list: for line in line_list:
if not destination_exchange_ratio and line.getDestinationTotalAssetPrice():
destination_exchange_ratio = line.getDestinationTotalAssetPrice() / line.getQuantity()
if not source_exchange_ratio and line.getSourceTotalAssetPrice():
source_exchange_ratio = line.getSourceTotalAssetPrice() / line.getQuantity()
for account in (line.getSourceValue(portal_type='Account'), for account in (line.getSourceValue(portal_type='Account'),
line.getDestinationValue(portal_type='Account'),): line.getDestinationValue(portal_type='Account'),):
account_type_dict.setdefault(line, set()).add( account_type_dict.setdefault(line, set()).add(
account is not None and account.getAccountTypeValue() or None) account is not None and account.getAccountTypeValue() or None)
# find asset line which will be used later
account_type = context.getPortalObject().portal_categories.account_type account_type = context.getPortalObject().portal_categories.account_type
receivable_type = account_type.asset.receivable receivable_type = account_type.asset.receivable
payable_type = account_type.liability.payable payable_type = account_type.liability.payable
line_to_adjust = None
asset_line = None asset_line = None
for line, account_type_list in account_type_dict.iteritems(): for line, account_type_list in account_type_dict.iteritems():
...@@ -62,20 +48,39 @@ for line, account_type_list in account_type_dict.iteritems(): ...@@ -62,20 +48,39 @@ for line, account_type_list in account_type_dict.iteritems():
asset_line = line asset_line = line
break break
if not asset_line: def roundLine(resource, get_method, set_method, exchange_ratio):
precision = context.getQuantityPrecisionFromResource(resource)
total_quantity = 0.0
for line in line_list:
line_quantity = round(getattr(line, get_method)(), precision)
getattr(line, set_method)(line_quantity)
total_quantity += line_quantity
abs_total_quantity = abs(round(total_quantity, precision))
# The total quantity should be zero with a little error, if simulation has been
# completely applied, because the debit and the credit must be balanced. However,
# this is not the case, if the delivery is divergent, as the builder does not
# adopt prevision automatically, when a conflict happens between the simulation
# and user-entered values.
if abs_total_quantity > 2 * context.restrictedTraverse(resource).getBaseUnitQuantity():
return
total_price = round(context.getTotalPrice() * exchange_ratio, precision)
if not asset_line:
assert total_price == 0.0 and total_quantity == 0.0, \ assert total_price == 0.0 and total_quantity == 0.0, \
'receivable or payable line not found.' 'receivable or payable line not found.'
return return
# If we have a difference between total credit and total debit, one line is # If we have a difference between total credit and total debit, one line is
# chosen to add or remove this difference. The payable or receivable is chosen # chosen to add or remove this difference. The payable or receivable is chosen
# only if this line is not matching with invoice total price, because total price # only if this line is not matching with invoice total price, because total price
# comes from all invoice lines (quantity * price) and it is what should be payed. # comes from all invoice lines (quantity * price) and it is what should be payed.
# And payable or receivable line is the record in the accounting of what has # And payable or receivable line is the record in the accounting of what has
# to be payed. Then, we must not touch it when it already matches. # to be payed. Then, we must not touch it when it already matches.
# If is not a payable or receivable, vat or other line (ie. income) is used. # If is not a payable or receivable, vat or other line (ie. income) is used.
if abs_total_quantity != 0: line_to_adjust = None
if round(abs(asset_line.getQuantity()), precision) != round(abs(context.getTotalPrice()), precision): if abs_total_quantity != 0:
if round(abs(getattr(asset_line, get_method)()), precision) != round(abs(context.getTotalPrice()) * exchange_ratio, precision):
# adjust payable or receivable # adjust payable or receivable
for line in line_list: for line in line_list:
if receivable_type in account_type_dict[line] or \ if receivable_type in account_type_dict[line] or \
...@@ -97,6 +102,20 @@ if abs_total_quantity != 0: ...@@ -97,6 +102,20 @@ if abs_total_quantity != 0:
line_to_adjust = line line_to_adjust = line
break break
if line_to_adjust is not None: if line_to_adjust is not None:
line_to_adjust.setQuantity( getattr(line_to_adjust, set_method)(
round(line_to_adjust.getQuantity() - total_quantity, precision)) round(getattr(line_to_adjust, get_method)() - total_quantity, precision))
resource = context.getResource()
# Round Debit/credit
roundLine(resource, 'getQuantity', 'setQuantity', 1)
# Round source asset price
if source_exchange_ratio:
source_section_price_currency = context.getSourceSectionValue().getPriceCurrency()
roundLine(source_section_price_currency, 'getSourceTotalAssetPrice', 'setSourceTotalAssetPrice', source_exchange_ratio)
# Round destination asset price
if destination_exchange_ratio:
destination_section_price_currency = context.getDestinationSectionValue().getPriceCurrency()
roundLine(destination_section_price_currency, 'getDestinationTotalAssetPrice', 'setDestinationTotalAssetPrice', destination_exchange_ratio)
...@@ -313,7 +313,7 @@ class TestConversionInSimulation(AccountingTestCase): ...@@ -313,7 +313,7 @@ class TestConversionInSimulation(AccountingTestCase):
delivery_movement.getPriceCurrencyValue()) delivery_movement.getPriceCurrencyValue())
self.assertEquals\ self.assertEquals\
(invoice_transaction_movement_1.getDestinationTotalAssetPrice(), (invoice_transaction_movement_1.getDestinationTotalAssetPrice(),
round(655.957*delivery_movement.getTotalPrice())) 655.957*invoice_transaction_movement_1.getTotalPrice())
self.assertEquals\ self.assertEquals\
(invoice_transaction_movement_1.getSourceTotalAssetPrice(), (invoice_transaction_movement_1.getSourceTotalAssetPrice(),
None) None)
...@@ -325,7 +325,7 @@ class TestConversionInSimulation(AccountingTestCase): ...@@ -325,7 +325,7 @@ class TestConversionInSimulation(AccountingTestCase):
delivery_movement.getPriceCurrencyValue()) delivery_movement.getPriceCurrencyValue())
self.assertEquals\ self.assertEquals\
(invoice_transaction_movement_2.getDestinationTotalAssetPrice(), (invoice_transaction_movement_2.getDestinationTotalAssetPrice(),
round(655.957*delivery_movement.getTotalPrice())) 655.957*invoice_transaction_movement_2.getTotalPrice())
def test_01_simulation_movement_source_asset_price(self,quiet=0, def test_01_simulation_movement_source_asset_price(self,quiet=0,
run=run_all_test): run=run_all_test):
...@@ -405,7 +405,7 @@ class TestConversionInSimulation(AccountingTestCase): ...@@ -405,7 +405,7 @@ class TestConversionInSimulation(AccountingTestCase):
delivery_movement.getPriceCurrencyValue()) delivery_movement.getPriceCurrencyValue())
self.assertEquals\ self.assertEquals\
(invoice_transaction_movement.getSourceTotalAssetPrice(), (invoice_transaction_movement.getSourceTotalAssetPrice(),
round(655.957*delivery_movement.getTotalPrice())) -655.957*invoice_transaction_movement.getTotalPrice())
self.assertEquals\ self.assertEquals\
(invoice_transaction_movement.getDestinationTotalAssetPrice(), (invoice_transaction_movement.getDestinationTotalAssetPrice(),
None) None)
...@@ -478,11 +478,6 @@ class TestConversionInSimulation(AccountingTestCase): ...@@ -478,11 +478,6 @@ class TestConversionInSimulation(AccountingTestCase):
related_packing_list.stop() related_packing_list.stop()
self.tic() self.tic()
self.buildInvoices() self.buildInvoices()
related_applied_rule = order.getCausalityRelatedValue(
portal_type='Applied Rule')
order_movement = related_applied_rule.contentValues()[0]
delivery_applied_rule = order_movement.contentValues()[0]
delivery_movement = delivery_applied_rule.contentValues()[0]
related_invoice = related_packing_list.getCausalityRelatedValue( related_invoice = related_packing_list.getCausalityRelatedValue(
portal_type='Sale Invoice Transaction') portal_type='Sale Invoice Transaction')
self.assertNotEquals(related_invoice, None) self.assertNotEquals(related_invoice, None)
...@@ -491,9 +486,113 @@ class TestConversionInSimulation(AccountingTestCase): ...@@ -491,9 +486,113 @@ class TestConversionInSimulation(AccountingTestCase):
line_list= related_invoice.contentValues( line_list= related_invoice.contentValues(
portal_type=self.portal.getPortalAccountingMovementTypeList()) portal_type=self.portal.getPortalAccountingMovementTypeList())
self.assertNotEquals(line_list, None) self.assertNotEquals(line_list, None)
result_list = []
for line in line_list:
result_list.append((line.getSource(), line.getDestinationTotalAssetPrice()))
self.assertEqual(line.getSourceTotalAssetPrice(), None)
self.assertEquals(
sorted(result_list),
sorted([
('account_module/customer', round(-2*(1+0.196)*655.957)),
('account_module/receivable_vat', round(2*0.196*655.957)),
('account_module/sale', round(2*655.957 ))
])
)
self.assertEqual(len(related_invoice.checkConsistency()), 0)
def test_01_source_total_asset_price_on_accounting_lines(self,quiet=0,
run=run_all_test):
"""
tests that the delivery builder of the invoice transaction lines
copies the source asset price on the accounting_lines of the invoice
"""
if not run: return
if not quiet:
printAndLog(
'test_01_source_total_asset_price_on_accounting_lines')
resource = self.portal.product_module.newContent(
portal_type='Product',
title='Resource',
product_line='apparel')
currency = self.portal.currency_module.newContent(
portal_type='Currency',
title='euro')
currency.setBaseUnitQuantity(0.01)
new_currency = \
self.portal.currency_module.newContent(portal_type='Currency')
new_currency.setReference('XOF')
new_currency.setTitle('Francs CFA')
new_currency.setBaseUnitQuantity(1.00)
self.tic()#execute transaction
x_curr_ex_line = currency.newContent(
portal_type='Currency Exchange Line',
price_currency=new_currency.getRelativeUrl())
x_curr_ex_line.setTitle('Euro to Francs CFA')
x_curr_ex_line.setBasePrice(655.957)
x_curr_ex_line.setStartDate(DateTime(2008,10,21))
x_curr_ex_line.setStopDate(DateTime(2008,10,22))
x_curr_ex_line.validate()
self.createBusinessProcess(currency)
self.tic()#execute transaction
client = self.portal.organisation_module.newContent(
portal_type='Organisation',
title='Client',
default_address_region=self.default_region)
vendor = self.portal.organisation_module.newContent(
portal_type='Organisation',
title='Vendor',
price_currency=new_currency.getRelativeUrl(),
default_address_region=self.default_region)
order = self.portal.sale_order_module.newContent(
portal_type='Sale Order',
source_value=vendor,
source_section_value=vendor,
destination_value=client,
destination_section_value=client,
start_date=DateTime(2008,10, 21),
price_currency_value=currency,
specialise_value=self.business_process,
title='Order')
order.newContent(portal_type='Sale Order Line',
resource_value=resource,
quantity=1,
price=2)
order.confirm()
self.tic()
self.buildPackingLists()
related_packing_list = order.getCausalityRelatedValue(
portal_type='Sale Packing List')
self.assertNotEquals(related_packing_list, None)
related_packing_list.start()
related_packing_list.stop()
self.tic()
self.buildInvoices()
related_invoice = related_packing_list.getCausalityRelatedValue(
portal_type='Sale Invoice Transaction')
self.assertNotEquals(related_invoice, None)
related_invoice.start()
self.tic()
line_list= related_invoice.contentValues(
portal_type=self.portal.getPortalAccountingMovementTypeList())
self.assertNotEquals(line_list, None)
result_list = []
for line in line_list: for line in line_list:
self.assertEqual(line.getDestinationTotalAssetPrice(), result_list.append((line.getSource(), line.getSourceTotalAssetPrice()))
round(655.957*delivery_movement.getTotalPrice())) self.assertEqual(line.getDestinationTotalAssetPrice(), None)
self.assertEquals(
sorted(result_list),
sorted([
('account_module/customer', round(2*(1+0.196)*655.957)),
('account_module/receivable_vat', round(-2*0.196*655.957)),
('account_module/sale', round(-2*655.957 ))
])
)
self.assertEqual(len(related_invoice.checkConsistency()), 0)
def test_01_diverged_sale_packing_list_destination_total_asset_price( def test_01_diverged_sale_packing_list_destination_total_asset_price(
self,quiet=0,run=run_all_test): self,quiet=0,run=run_all_test):
...@@ -565,8 +664,6 @@ class TestConversionInSimulation(AccountingTestCase): ...@@ -565,8 +664,6 @@ class TestConversionInSimulation(AccountingTestCase):
related_packing_list_line_list=related_packing_list.getMovementList() related_packing_list_line_list=related_packing_list.getMovementList()
related_packing_list_line= related_packing_list_line_list[0] related_packing_list_line= related_packing_list_line_list[0]
self.assertEqual(related_packing_list_line.getQuantity(),5.0) self.assertEqual(related_packing_list_line.getQuantity(),5.0)
old_destination_asset_price = \
round(655.957*related_packing_list_line.getTotalPrice())
related_packing_list_line.edit(quantity=3.0) related_packing_list_line.edit(quantity=3.0)
self.tic() self.tic()
...@@ -587,11 +684,17 @@ class TestConversionInSimulation(AccountingTestCase): ...@@ -587,11 +684,17 @@ class TestConversionInSimulation(AccountingTestCase):
invoice_applied_rule = delivery_movement.contentValues()[0] invoice_applied_rule = delivery_movement.contentValues()[0]
invoice_movement = invoice_applied_rule.contentValues()[0] invoice_movement = invoice_applied_rule.contentValues()[0]
invoice_transaction_applied_rule = invoice_movement.contentValues()[0] invoice_transaction_applied_rule = invoice_movement.contentValues()[0]
invoice_transaction_movement =\ result_list = []
invoice_transaction_applied_rule.contentValues()[0] for invoice_transaction_movement in invoice_transaction_applied_rule.contentValues():
self.assertEqual( result_list.append((invoice_transaction_movement.getSource(), invoice_transaction_movement.getDestinationTotalAssetPrice()))
invoice_transaction_movement.getDestinationTotalAssetPrice(), self.assertEquals(
old_destination_asset_price *(3.0/5.0)) sorted(result_list),
sorted([
('account_module/customer', -2*3*(1+0.196)*655.957),
('account_module/receivable_vat', 2*3*0.196*655.957),
('account_module/sale', 2*3*655.957 )
])
)
def test_01_diverged_purchase_packing_list_source_total_asset_price( def test_01_diverged_purchase_packing_list_source_total_asset_price(
...@@ -664,8 +767,6 @@ class TestConversionInSimulation(AccountingTestCase): ...@@ -664,8 +767,6 @@ class TestConversionInSimulation(AccountingTestCase):
related_packing_list_line_list=related_packing_list.getMovementList() related_packing_list_line_list=related_packing_list.getMovementList()
related_packing_list_line= related_packing_list_line_list[0] related_packing_list_line= related_packing_list_line_list[0]
self.assertEqual(related_packing_list_line.getQuantity(),5.0) self.assertEqual(related_packing_list_line.getQuantity(),5.0)
old_source_asset_price = \
round(655.957*related_packing_list_line.getTotalPrice())
related_packing_list_line.edit(quantity=3.0) related_packing_list_line.edit(quantity=3.0)
self.tic() self.tic()
...@@ -687,11 +788,17 @@ class TestConversionInSimulation(AccountingTestCase): ...@@ -687,11 +788,17 @@ class TestConversionInSimulation(AccountingTestCase):
invoice_applied_rule = delivery_movement.contentValues()[0] invoice_applied_rule = delivery_movement.contentValues()[0]
invoice_movement = invoice_applied_rule.contentValues()[0] invoice_movement = invoice_applied_rule.contentValues()[0]
invoice_transaction_applied_rule = invoice_movement.contentValues()[0] invoice_transaction_applied_rule = invoice_movement.contentValues()[0]
invoice_transaction_movement =\ result_list = []
invoice_transaction_applied_rule.contentValues()[0] for invoice_transaction_movement in invoice_transaction_applied_rule.contentValues():
self.assertEqual(invoice_transaction_movement.\ result_list.append((invoice_transaction_movement.getSource(), invoice_transaction_movement.getSourceTotalAssetPrice()))
getSourceTotalAssetPrice(), self.assertEquals(
old_source_asset_price *(3.0/5.0)) sorted(result_list),
sorted([
('account_module/customer', 2*3*(1+0.196)*655.957),
('account_module/receivable_vat', -2*3*0.196*655.957),
('account_module/sale', -2*3*655.957 )
])
)
def test_01_delivery_mode_on_sale_packing_list_and_invoice( def test_01_delivery_mode_on_sale_packing_list_and_invoice(
self,quiet=0,run=run_all_test): self,quiet=0,run=run_all_test):
......
...@@ -124,12 +124,16 @@ class InvoiceTransactionRuleMovementGenerator(MovementGeneratorMixin): ...@@ -124,12 +124,16 @@ class InvoiceTransactionRuleMovementGenerator(MovementGeneratorMixin):
.getParentValue().getParentValue() .getParentValue().getParentValue()
kw = {'delivery': None, 'resource': resource, 'price': 1} kw = {'delivery': None, 'resource': resource, 'price': 1}
return kw
if resource is not None: def getGeneratedMovementList(self, movement_list=None, rounding=False):
#set asset_price on movement when resource is different from price movement_list = super(InvoiceTransactionRuleMovementGenerator, self).getGeneratedMovementList(movement_list=movement_list, rounding=rounding)
#currency of the source/destination section portal = self._applied_rule.getPortalObject()
for arrow in 'destination', 'source': for arrow in 'destination', 'source':
section = input_movement.getDefaultAcquiredValue(arrow + '_section') for movement in movement_list:
resource = movement.getResource()
if resource is not None:
section = movement.getDefaultAcquiredValue(arrow + '_section')
if section is not None: if section is not None:
try: try:
currency_url = section.getPriceCurrency() currency_url = section.getPriceCurrency()
...@@ -138,15 +142,17 @@ class InvoiceTransactionRuleMovementGenerator(MovementGeneratorMixin): ...@@ -138,15 +142,17 @@ class InvoiceTransactionRuleMovementGenerator(MovementGeneratorMixin):
if currency_url not in (None, resource): if currency_url not in (None, resource):
currency = portal.unrestrictedTraverse(currency_url) currency = portal.unrestrictedTraverse(currency_url)
exchange_ratio = currency.getPrice( exchange_ratio = currency.getPrice(
context=input_movement.asContext( context=movement.asContext(
categories=('price_currency/' + currency_url, categories=('price_currency/' + currency_url,
'resource/' + resource))) 'resource/' + resource)))
if exchange_ratio is not None: if exchange_ratio is not None:
kw[arrow + '_total_asset_price'] = round( if arrow == 'destination':
exchange_ratio * input_movement.getQuantity(), sign = 1
currency.getQuantityPrecision()) else:
sign = -1
movement.setProperty(arrow + '_total_asset_price', movement.getQuantity() * exchange_ratio * sign)
return kw return movement_list
def _getInputMovementList(self, movement_list=None, rounding=False): def _getInputMovementList(self, movement_list=None, rounding=False):
simulation_movement = self._applied_rule.getParentValue() simulation_movement = self._applied_rule.getParentValue()
......
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