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
7
Merge Requests
7
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Jobs
Commits
Open sidebar
Jérome Perrin
erp5
Commits
7505e054
Commit
7505e054
authored
May 22, 2019
by
Xiaowu Zhang
Browse files
Options
Browse Files
Download
Plain Diff
erp5_invoicing: enable "Merge Deliveries" action on accounting module
/reviewed-on
nexedi/erp5!876
parents
17c0aa5a
d2f4dd44
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
236 additions
and
83 deletions
+236
-83
bt5/erp5_invoicing/ActionTemplateItem/portal_types/Accounting%20Transaction%20Module/merge_delivery_list.xml
...Accounting%20Transaction%20Module/merge_delivery_list.xml
+85
-0
bt5/erp5_invoicing/bt/template_action_path_list
bt5/erp5_invoicing/bt/template_action_path_list
+1
-0
bt5/erp5_simulation/SkinTemplateItem/portal_skins/erp5_simulation/DeliveryModule_mergeDeliveryList.py
...skins/erp5_simulation/DeliveryModule_mergeDeliveryList.py
+9
-6
product/ERP5/Tool/SimulationTool.py
product/ERP5/Tool/SimulationTool.py
+84
-77
product/ERP5/tests/testInvoice.py
product/ERP5/tests/testInvoice.py
+57
-0
No files found.
bt5/erp5_invoicing/ActionTemplateItem/portal_types/Accounting%20Transaction%20Module/merge_delivery_list.xml
0 → 100644
View file @
7505e054
<?xml version="1.0"?>
<ZopeData>
<record
id=
"1"
aka=
"AAAAAAAAAAE="
>
<pickle>
<global
name=
"ActionInformation"
module=
"Products.CMFCore.ActionInformation"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
action
</string>
</key>
<value>
<persistent>
<string
encoding=
"base64"
>
AAAAAAAAAAI=
</string>
</persistent>
</value>
</item>
<item>
<key>
<string>
categories
</string>
</key>
<value>
<tuple>
<string>
action_type/object_action
</string>
</tuple>
</value>
</item>
<item>
<key>
<string>
category
</string>
</key>
<value>
<string>
object_action
</string>
</value>
</item>
<item>
<key>
<string>
condition
</string>
</key>
<value>
<string></string>
</value>
</item>
<item>
<key>
<string>
description
</string>
</key>
<value>
<none/>
</value>
</item>
<item>
<key>
<string>
icon
</string>
</key>
<value>
<string></string>
</value>
</item>
<item>
<key>
<string>
id
</string>
</key>
<value>
<string>
merge_delivery_list
</string>
</value>
</item>
<item>
<key>
<string>
permissions
</string>
</key>
<value>
<tuple>
<string>
Add portal content
</string>
</tuple>
</value>
</item>
<item>
<key>
<string>
portal_type
</string>
</key>
<value>
<string>
Action Information
</string>
</value>
</item>
<item>
<key>
<string>
priority
</string>
</key>
<value>
<float>
5.0
</float>
</value>
</item>
<item>
<key>
<string>
title
</string>
</key>
<value>
<string>
Merge Deliveries
</string>
</value>
</item>
<item>
<key>
<string>
visible
</string>
</key>
<value>
<int>
1
</int>
</value>
</item>
</dictionary>
</pickle>
</record>
<record
id=
"2"
aka=
"AAAAAAAAAAI="
>
<pickle>
<global
name=
"Expression"
module=
"Products.CMFCore.Expression"
/>
</pickle>
<pickle>
<dictionary>
<item>
<key>
<string>
text
</string>
</key>
<value>
<string>
string:${object_url}/DeliveryModule_mergeDeliveryList
</string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_invoicing/bt/template_action_path_list
View file @
7505e054
Accounting Transaction Module | merge_delivery_list
Invoice Line | price
Invoice Line | quantity
Invoice Line | view
...
...
bt5/erp5_simulation/SkinTemplateItem/portal_skins/erp5_simulation/DeliveryModule_mergeDeliveryList.py
View file @
7505e054
...
...
@@ -5,12 +5,15 @@ delivery_list = []
for
o
in
object_list
:
delivery_list
.
append
(
o
)
Base_translateString
=
context
.
Base_translateString
if
len
(
delivery_list
)
<
2
:
ret_url
=
context
.
absolute_url
()
+
'/'
+
form_id
qs
=
'?portal_status_message=Please+select+more+than+one+items.'
message
=
Base_translateString
(
'Please select more than one items.'
)
else
:
ret_url
=
context
.
absolute_url
()
+
'/'
+
form_id
qs
=
'?portal_status_message=Merged.'
context
.
portal_simulation
.
mergeDeliveryList
(
delivery_list
)
error_list
=
context
.
portal_simulation
.
mergeDeliveryList
(
delivery_list
)
if
not
error_list
:
message
=
Base_translateString
(
'Merged.'
)
else
:
message
=
' '
.
join
([
str
(
x
)
for
x
in
error_list
])
return
REQUEST
.
RESPONSE
.
redirect
(
ret_url
+
qs
)
return
context
.
Base_redirect
(
'view'
,
keep_items
=
{
'portal_status_message'
:
message
}
)
product/ERP5/Tool/SimulationTool.py
View file @
7505e054
...
...
@@ -2647,7 +2647,7 @@ class SimulationTool(BaseTool):
return
result
def
_findBuilderForDelivery
(
self
,
delivery
):
def
_findBuilderForDelivery
(
self
,
delivery
,
movement_portal_type
):
"""
Find out the builder corresponding to a delivery by looking at the business process
"""
...
...
@@ -2655,7 +2655,8 @@ class SimulationTool(BaseTool):
portal_type
=
delivery
.
getPortalType
()
for
business_link
in
delivery
.
asComposedDocument
().
objectValues
(
portal_type
=
"Business Link"
):
for
business_link_builder
in
business_link
.
getDeliveryBuilderValueList
():
if
business_link_builder
.
getDeliveryPortalType
()
==
portal_type
:
if
business_link_builder
.
getDeliveryPortalType
()
==
portal_type
\
and
business_link_builder
.
getDeliveryLinePortalType
()
==
movement_portal_type
:
builder
=
business_link_builder
break
if
builder
is
not
None
:
...
...
@@ -2681,87 +2682,93 @@ class SimulationTool(BaseTool):
if
len
(
portal_type_set
)
!=
1
:
error_list
.
append
(
translateString
(
"Please select only deliveries of same type"
))
else
:
portal_type
,
=
portal_type_set
allowed_state_set
=
set
(
portal
.
getPortalReservedInventoryStateList
()
+
\
portal
.
getPortalFutureInventoryStateList
())
found_state_set
=
set
([
x
.
getSimulationState
()
for
x
in
delivery_list
])
if
found_state_set
.
difference
(
allowed_state_set
):
error_list
.
append
(
translateString
(
"Found delivery having unexpected status for merge"
))
else
:
# Allow to call a script to do custom checking conditions before merge
main_delivery
=
delivery_list
[
0
]
check_merge_condition_method
=
main_delivery
.
_getTypeBasedMethod
(
"checkMergeConditionOnDeliveryList"
)
if
check_merge_condition_method
is
not
None
:
error_list
.
extend
(
check_merge_condition_method
(
delivery_list
=
delivery_list
))
if
len
(
error_list
)
==
0
:
# so far so good
# in delivery_list we have list of delivery to merge
simulation_movement_list
=
[]
to_copy_delivery_line_list
=
[]
# for lines not coming from upper simulation, thus
# created by hand should be manually added to main
# delivery since they are not coming from builder
for
delivery
in
delivery_list
:
line_id_to_delete_list
=
[]
for
movement
in
delivery
.
getMovementList
():
related_simulation_movement_list
=
movement
.
getDeliveryRelatedValueList
()
for
simulation_movement
in
related_simulation_movement_list
:
# if we are on a root applied rule directly, so in the case of
# a manually added line, we have to copy
# the simulation movement into to main delivery
if
simulation_movement
.
getParentValue
().
getParentValue
().
getId
()
==
"portal_simulation"
:
# For manually added lines, make sure we have only one simulation movement
assert
len
(
related_simulation_movement_list
)
==
1
if
not
(
delivery
is
main_delivery
):
to_copy_delivery_line_list
.
append
(
movement
)
else
:
simulation_movement
.
setDeliveryValue
(
None
)
simulation_movement_list
.
append
(
simulation_movement
)
# Since we keep the main delivery, we remove existing lines already
# coming from builder to let builder recreate them in the same time
# as other ones (to possibly merge lines also)
movement_id
=
movement
.
getId
()
if
delivery
is
main_delivery
and
not
(
movement_id
in
line_id_to_delete_list
):
line_id_to_delete_list
.
append
(
movement
.
getId
())
if
line_id_to_delete_list
:
delivery
.
manage_delObjects
(
ids
=
line_id_to_delete_list
)
# It is required to expand again simulation movement, because
# we unlinked them from delivery, so it is possible that some
# properties will change on simulation movement (mostly categories).
# By expanding again, we will avoid having many deliveries instead
# of one when doing "merge"
for
simulation_movement
in
simulation_movement_list
:
simulation_movement
.
expand
(
expand_policy
=
'immediate'
)
# activate builder
merged_builder
=
self
.
_findBuilderForDelivery
(
main_delivery
)
if
merged_builder
is
None
:
error_list
.
append
(
translateString
(
"Unable to find builder"
))
else
:
merged_builder
.
build
(
movement_relative_url_list
=
[
q
.
getRelativeUrl
()
for
q
in
\
simulation_movement_list
],
merge_delivery
=
True
,
delivery_relative_url_list
=
[
main_delivery
.
getRelativeUrl
()])
# Finally, copy all lines that were created manually on all deliveries except
# the main one
@
UnrestrictedMethod
def
setMainDeliveryModifiable
(
delivery
):
# set causality state in such way we can modify delivery
delivery
.
diverge
()
setMainDeliveryModifiable
(
main_delivery
)
delivery_type_list
=
portal
.
getPortalDeliveryTypeList
()
for
delivery_line
in
to_copy_delivery_line_list
:
delivery
=
delivery_line
.
getParentValue
()
if
not
(
delivery
.
getPortalType
()
in
delivery_type_list
):
raise
NotImplementedError
(
"Merge of deliveries doe not yet handle case of cells"
)
copy_data
=
delivery
.
manage_copyObjects
(
ids
=
[
delivery_line
.
getId
()])
main_delivery
.
manage_pasteObjects
(
copy_data
)
main_delivery
.
updateCausalityState
()
# Finally do cleanup
for
delivery
in
delivery_list
[
1
:]:
# cancel, delete - to disallow any user related operations on those deliveries
after_merge_method
=
delivery
.
_getTypeBasedMethod
(
'cleanDeliveryAfterMerge'
)
if
after_merge_method
is
not
None
:
after_merge_method
()
movement_portal_type_set
=
set
()
for
delivery
in
delivery_list
:
movement_portal_type_set
.
update
([
x
.
getPortalType
()
for
x
in
delivery
.
getMovementList
()])
if
len
(
movement_portal_type_set
)
!=
1
:
error_list
.
append
(
translateString
(
"Please Select only movement of same type"
))
else
:
# Allow to call a script to do custom checking conditions before merge
main_delivery
=
delivery_list
[
0
]
check_merge_condition_method
=
main_delivery
.
_getTypeBasedMethod
(
"checkMergeConditionOnDeliveryList"
)
if
check_merge_condition_method
is
not
None
:
error_list
.
extend
(
check_merge_condition_method
(
delivery_list
=
delivery_list
))
if
len
(
error_list
)
==
0
:
# so far so good
# in delivery_list we have list of delivery to merge
simulation_movement_list
=
[]
to_copy_delivery_line_list
=
[]
# for lines not coming from upper simulation, thus
# created by hand should be manually added to main
# delivery since they are not coming from builder
for
delivery
in
delivery_list
:
line_id_to_delete_list
=
[]
for
movement
in
delivery
.
getMovementList
():
related_simulation_movement_list
=
movement
.
getDeliveryRelatedValueList
()
for
simulation_movement
in
related_simulation_movement_list
:
# if we are on a root applied rule directly, so in the case of
# a manually added line, we have to copy
# the simulation movement into to main delivery
if
simulation_movement
.
getParentValue
().
getParentValue
().
getId
()
==
"portal_simulation"
:
# For manually added lines, make sure we have only one simulation movement
assert
len
(
related_simulation_movement_list
)
==
1
if
not
(
delivery
is
main_delivery
):
to_copy_delivery_line_list
.
append
(
movement
)
else
:
simulation_movement
.
setDeliveryValue
(
None
)
simulation_movement_list
.
append
(
simulation_movement
)
# Since we keep the main delivery, we remove existing lines already
# coming from builder to let builder recreate them in the same time
# as other ones (to possibly merge lines also)
movement_id
=
movement
.
getId
()
if
delivery
is
main_delivery
and
not
(
movement_id
in
line_id_to_delete_list
):
line_id_to_delete_list
.
append
(
movement
.
getId
())
if
line_id_to_delete_list
:
delivery
.
manage_delObjects
(
ids
=
line_id_to_delete_list
)
# It is required to expand again simulation movement, because
# we unlinked them from delivery, so it is possible that some
# properties will change on simulation movement (mostly categories).
# By expanding again, we will avoid having many deliveries instead
# of one when doing "merge"
for
simulation_movement
in
simulation_movement_list
:
simulation_movement
.
expand
(
expand_policy
=
'immediate'
)
# activate builder
movement_portal_type
,
=
movement_portal_type_set
merged_builder
=
self
.
_findBuilderForDelivery
(
main_delivery
,
movement_portal_type
)
if
merged_builder
is
None
:
error_list
.
append
(
translateString
(
"Unable to find builder"
))
else
:
merged_builder
.
build
(
movement_relative_url_list
=
[
q
.
getRelativeUrl
()
for
q
in
\
simulation_movement_list
],
merge_delivery
=
True
,
delivery_relative_url_list
=
[
main_delivery
.
getRelativeUrl
()])
# Finally, copy all lines that were created manually on all deliveries except
# the main one
@
UnrestrictedMethod
def
setMainDeliveryModifiable
(
delivery
):
# set causality state in such way we can modify delivery
delivery
.
diverge
()
setMainDeliveryModifiable
(
main_delivery
)
delivery_type_list
=
portal
.
getPortalDeliveryTypeList
()
for
delivery_line
in
to_copy_delivery_line_list
:
delivery
=
delivery_line
.
getParentValue
()
if
not
(
delivery
.
getPortalType
()
in
delivery_type_list
):
raise
NotImplementedError
(
"Merge of deliveries doe not yet handle case of cells"
)
copy_data
=
delivery
.
manage_copyObjects
(
ids
=
[
delivery_line
.
getId
()])
main_delivery
.
manage_pasteObjects
(
copy_data
)
main_delivery
.
updateCausalityState
()
# Finally do cleanup
for
delivery
in
delivery_list
[
1
:]:
# cancel, delete - to disallow any user related operations on those deliveries
after_merge_method
=
delivery
.
_getTypeBasedMethod
(
'cleanDeliveryAfterMerge'
)
if
after_merge_method
is
not
None
:
after_merge_method
()
else
:
error_list
.
append
(
translateString
(
"Please select at least two deliveries"
))
return
error_list
...
...
product/ERP5/tests/testInvoice.py
View file @
7505e054
...
...
@@ -2471,6 +2471,63 @@ self.portal.getDefaultModule(self.packing_list_portal_type).newContent(
self
.
assertEqual
([],
packing_list
.
getDivergenceList
())
self
.
assertEqual
(
'solved'
,
packing_list
.
getCausalityState
())
def
test_merge_accounting_invoice
(
self
,
quiet
=
quiet
):
sequence_list
=
SequenceList
()
sequence
=
sequence_list
.
addSequenceString
(
self
.
PACKING_LIST_DEFAULT_SEQUENCE
)
sequence_list
.
play
(
self
,
quiet
=
quiet
)
packing_list
=
sequence
.
get
(
'packing_list'
)
packing_list_line
=
packing_list
.
getMovementList
()[
0
]
quantity
=
packing_list_line
.
getQuantity
()
resource
=
packing_list_line
.
getResource
()
price
=
packing_list_line
.
getPrice
()
packing_list
.
setReady
()
packing_list
.
start
()
packing_list
.
stop
()
self
.
tic
()
self
.
default_quantity
=
self
.
default_quantity
+
10
self
.
default_price
=
self
.
default_price
+
10
self
.
tic
()
sequence_list
.
play
(
self
,
quiet
=
quiet
)
packing_list_2
=
sequence
.
get
(
'packing_list'
)
packing_list_line
=
packing_list_2
.
getMovementList
()[
0
]
quantity_2
=
packing_list_line
.
getQuantity
()
resource_2
=
packing_list_line
.
getResource
()
price_2
=
packing_list_line
.
getPrice
()
packing_list_2
.
setReady
()
packing_list_2
.
start
()
packing_list_2
.
stop
()
self
.
tic
()
self
.
stepInvoiceBuilderAlarm
()
self
.
tic
()
self
.
default_quantity
=
self
.
default_quantity
-
10
self
.
default_price
=
self
.
default_price
-
10
invoice
=
packing_list
.
getCausalityRelatedValue
(
portal_type
=
self
.
invoice_portal_type
)
invoice_2
=
packing_list_2
.
getCausalityRelatedValue
(
portal_type
=
self
.
invoice_portal_type
)
self
.
assertNotEquals
(
invoice
,
None
)
self
.
assertNotEquals
(
invoice_2
,
None
)
self
.
tic
()
error_list
=
self
.
portal
.
portal_simulation
.
mergeDeliveryList
([
invoice
,
invoice_2
])
self
.
tic
()
self
.
assertEqual
(
0
,
len
(
error_list
))
self
.
assertEqual
(
invoice
.
getSimulationState
(),
'confirmed'
)
# MergeDeliveryList change the first delivery to diverged
# Make sure it works as expected
self
.
assertEqual
(
invoice
.
getCausalityState
(),
'diverged'
)
self
.
assertEqual
(
invoice_2
.
getSimulationState
(),
'cancelled'
)
self
.
assertEqual
(
len
(
invoice
.
getMovementList
()),
2
)
expected_set
=
set
([
(
resource
,
quantity
,
price
),
(
resource_2
,
quantity_2
,
price_2
)])
result_set
=
set
(
sorted
(
[(
x
.
getResource
(),
x
.
getQuantity
(),
x
.
getPrice
())
for
x
in
invoice
.
getMovementList
()],
key
=
lambda
x
:
x
[
1
]))
self
.
assertEqual
(
expected_set
,
result_set
)
def
test_subcontent_reindexing
(
self
):
"""Tests, that modification on Order are propagated to lines and cells
...
...
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