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
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
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
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Paul Graydon
erp5
Commits
079b8a74
Commit
079b8a74
authored
2 years ago
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
Changes
3
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
)
This diff is collapsed.
Click to expand it.
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
):
...
...
This diff is collapsed.
Click to expand it.
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
()
...
...
This diff is collapsed.
Click to expand it.
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