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
""" Rounds debit and credit lines on generated transactions, according to
precision of this transaction's resource.
precision of this transaction's resource.
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
amount, but we have ignored this.
"""
precision = context.getQuantityPrecisionFromResource(context.getResource())
resource = context.getResourceValue()
line = None
total_quantity = 0.0
line_list = context.getMovementList(
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
# 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
......@@ -31,28 +19,26 @@ for line in line_list:
if not line_list:
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 = {}
source_exchange_ratio = None
destination_exchange_ratio = None
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'),
line.getDestinationValue(portal_type='Account'),):
account_type_dict.setdefault(line, set()).add(
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
receivable_type = account_type.asset.receivable
payable_type = account_type.liability.payable
line_to_adjust = None
asset_line = None
for line, account_type_list in account_type_dict.iteritems():
......@@ -62,41 +48,74 @@ for line, account_type_list in account_type_dict.iteritems():
asset_line = line
break
if not asset_line:
assert total_price == 0.0 and total_quantity == 0.0, \
'receivable or payable line not found.'
return
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, \
'receivable or payable line not found.'
return
# 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
# 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.
# 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.
# If is not a payable or receivable, vat or other line (ie. income) is used.
line_to_adjust = None
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
for line in line_list:
if receivable_type in account_type_dict[line] or \
payable_type in account_type_dict[line]:
line_to_adjust = line
break
if line_to_adjust is None:
# VAT
for line in line_list:
if receivable_type.refundable_vat in account_type_dict[line] or \
payable_type.collected_vat in account_type_dict[line]:
line_to_adjust = line
break
if line_to_adjust is None:
# adjust anything except payable or receivable
for line in line_list:
if receivable_type not in account_type_dict[line] and \
payable_type not in account_type_dict[line]:
line_to_adjust = line
break
if line_to_adjust is not None:
getattr(line_to_adjust, set_method)(
round(getattr(line_to_adjust, get_method)() - total_quantity, precision))
# 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
# 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.
# 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.
# If is not a payable or receivable, vat or other line (ie. income) is used.
if abs_total_quantity != 0:
if round(abs(asset_line.getQuantity()), precision) != round(abs(context.getTotalPrice()), precision):
# adjust payable or receivable
for line in line_list:
if receivable_type in account_type_dict[line] or \
payable_type in account_type_dict[line]:
line_to_adjust = line
break
if line_to_adjust is None:
# VAT
for line in line_list:
if receivable_type.refundable_vat in account_type_dict[line] or \
payable_type.collected_vat in account_type_dict[line]:
line_to_adjust = line
break
if line_to_adjust is None:
# adjust anything except payable or receivable
for line in line_list:
if receivable_type not in account_type_dict[line] and \
payable_type not in account_type_dict[line]:
line_to_adjust = line
break
if line_to_adjust is not None:
line_to_adjust.setQuantity(
round(line_to_adjust.getQuantity() - 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):
delivery_movement.getPriceCurrencyValue())
self.assertEquals\
(invoice_transaction_movement_1.getDestinationTotalAssetPrice(),
round(655.957*delivery_movement.getTotalPrice()))
655.957*invoice_transaction_movement_1.getTotalPrice())
self.assertEquals\
(invoice_transaction_movement_1.getSourceTotalAssetPrice(),
None)
......@@ -325,7 +325,7 @@ class TestConversionInSimulation(AccountingTestCase):
delivery_movement.getPriceCurrencyValue())
self.assertEquals\
(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,
run=run_all_test):
......@@ -405,7 +405,7 @@ class TestConversionInSimulation(AccountingTestCase):
delivery_movement.getPriceCurrencyValue())
self.assertEquals\
(invoice_transaction_movement.getSourceTotalAssetPrice(),
round(655.957*delivery_movement.getTotalPrice()))
-655.957*invoice_transaction_movement.getTotalPrice())
self.assertEquals\
(invoice_transaction_movement.getDestinationTotalAssetPrice(),
None)
......@@ -478,11 +478,6 @@ class TestConversionInSimulation(AccountingTestCase):
related_packing_list.stop()
self.tic()
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(
portal_type='Sale Invoice Transaction')
self.assertNotEquals(related_invoice, None)
......@@ -491,9 +486,113 @@ class TestConversionInSimulation(AccountingTestCase):
line_list= related_invoice.contentValues(
portal_type=self.portal.getPortalAccountingMovementTypeList())
self.assertNotEquals(line_list, None)
result_list = []
for line in line_list:
self.assertEqual(line.getDestinationTotalAssetPrice(),
round(655.957*delivery_movement.getTotalPrice()))
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:
result_list.append((line.getSource(), line.getSourceTotalAssetPrice()))
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(
self,quiet=0,run=run_all_test):
......@@ -565,8 +664,6 @@ class TestConversionInSimulation(AccountingTestCase):
related_packing_list_line_list=related_packing_list.getMovementList()
related_packing_list_line= related_packing_list_line_list[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)
self.tic()
......@@ -587,11 +684,17 @@ class TestConversionInSimulation(AccountingTestCase):
invoice_applied_rule = delivery_movement.contentValues()[0]
invoice_movement = invoice_applied_rule.contentValues()[0]
invoice_transaction_applied_rule = invoice_movement.contentValues()[0]
invoice_transaction_movement =\
invoice_transaction_applied_rule.contentValues()[0]
self.assertEqual(
invoice_transaction_movement.getDestinationTotalAssetPrice(),
old_destination_asset_price *(3.0/5.0))
result_list = []
for invoice_transaction_movement in invoice_transaction_applied_rule.contentValues():
result_list.append((invoice_transaction_movement.getSource(), invoice_transaction_movement.getDestinationTotalAssetPrice()))
self.assertEquals(
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(
......@@ -664,8 +767,6 @@ class TestConversionInSimulation(AccountingTestCase):
related_packing_list_line_list=related_packing_list.getMovementList()
related_packing_list_line= related_packing_list_line_list[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)
self.tic()
......@@ -687,11 +788,17 @@ class TestConversionInSimulation(AccountingTestCase):
invoice_applied_rule = delivery_movement.contentValues()[0]
invoice_movement = invoice_applied_rule.contentValues()[0]
invoice_transaction_applied_rule = invoice_movement.contentValues()[0]
invoice_transaction_movement =\
invoice_transaction_applied_rule.contentValues()[0]
self.assertEqual(invoice_transaction_movement.\
getSourceTotalAssetPrice(),
old_source_asset_price *(3.0/5.0))
result_list = []
for invoice_transaction_movement in invoice_transaction_applied_rule.contentValues():
result_list.append((invoice_transaction_movement.getSource(), invoice_transaction_movement.getSourceTotalAssetPrice()))
self.assertEquals(
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(
self,quiet=0,run=run_all_test):
......
......@@ -124,29 +124,35 @@ class InvoiceTransactionRuleMovementGenerator(MovementGeneratorMixin):
.getParentValue().getParentValue()
kw = {'delivery': None, 'resource': resource, 'price': 1}
return kw
if resource is not None:
#set asset_price on movement when resource is different from price
#currency of the source/destination section
for arrow in 'destination', 'source':
section = input_movement.getDefaultAcquiredValue(arrow + '_section')
if section is not None:
try:
currency_url = section.getPriceCurrency()
except AttributeError:
currency_url = None
if currency_url not in (None, resource):
currency = portal.unrestrictedTraverse(currency_url)
exchange_ratio = currency.getPrice(
context=input_movement.asContext(
categories=('price_currency/' + currency_url,
'resource/' + resource)))
if exchange_ratio is not None:
kw[arrow + '_total_asset_price'] = round(
exchange_ratio * input_movement.getQuantity(),
currency.getQuantityPrecision())
def getGeneratedMovementList(self, movement_list=None, rounding=False):
movement_list = super(InvoiceTransactionRuleMovementGenerator, self).getGeneratedMovementList(movement_list=movement_list, rounding=rounding)
portal = self._applied_rule.getPortalObject()
for arrow in 'destination', 'source':
for movement in movement_list:
resource = movement.getResource()
if resource is not None:
section = movement.getDefaultAcquiredValue(arrow + '_section')
if section is not None:
try:
currency_url = section.getPriceCurrency()
except AttributeError:
currency_url = None
if currency_url not in (None, resource):
currency = portal.unrestrictedTraverse(currency_url)
exchange_ratio = currency.getPrice(
context=movement.asContext(
categories=('price_currency/' + currency_url,
'resource/' + resource)))
if exchange_ratio is not None:
if arrow == 'destination':
sign = 1
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):
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