Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
erp5
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
138
Merge Requests
138
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Jobs
Commits
Open sidebar
nexedi
erp5
Commits
079b8a74
Commit
079b8a74
authored
May 02, 2022
by
Xiaowu Zhang
Browse files
Options
Browse Files
Download
Plain Diff
fix asset price on accounting line
See merge request
nexedi/erp5!1611
parents
f8d826d4
b7f87dac
Pipeline
#21229
failed with stage
in 0 seconds
Changes
3
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
238 additions
and
106 deletions
+238
-106
bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountingTransaction_roundDebitCredit.py
...erp5_accounting/AccountingTransaction_roundDebitCredit.py
+80
-61
bt5/erp5_accounting_ui_test/TestTemplateItem/portal_components/test.erp5.testConversionInSimulation.py
...portal_components/test.erp5.testConversionInSimulation.py
+131
-24
bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.InvoiceTransactionSimulationRule.py
...ponents/document.erp5.InvoiceTransactionSimulationRule.py
+27
-21
No files found.
bt5/erp5_accounting/SkinTemplateItem/portal_skins/erp5_accounting/AccountingTransaction_roundDebitCredit.py
View file @
079b8a74
""" 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
)
bt5/erp5_accounting_ui_test/TestTemplateItem/portal_components/test.erp5.testConversionInSimulation.py
View file @
079b8a74
...
...
@@ -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
):
...
...
bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.InvoiceTransactionSimulationRule.py
View file @
079b8a74
...
...
@@ -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
()
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment