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

fix asset price on accounting line

See merge request !1611
parents f8d826d4 b7f87dac
Pipeline #21229 failed with stage
in 0 seconds
""" 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