forked from OCA/purchase-workflow
-
Notifications
You must be signed in to change notification settings - Fork 0
/
purchase_group_hooks.py
236 lines (198 loc) · 8.97 KB
/
purchase_group_hooks.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Leonardo Pistone
# Copyright 2014 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv.orm import Model
from openerp import netsvc
from openerp.osv.orm import browse_record, browse_null
class PurchaseOrder(Model):
_inherit = 'purchase.order'
def _key_fields_for_grouping(self):
"""Return a list of fields used to identify orders that can be merged.
Orders that have this fields equal can be merged.
This function can be extended by other modules to modify the list.
"""
return ('partner_id', 'location_id', 'pricelist_id')
def _key_fields_for_grouping_lines(self):
"""Return a list of fields used to identify order lines that can be
merged.
Lines that have this fields equal can be merged.
This function can be extended by other modules to modify the list.
"""
return ('name', 'date_planned', 'taxes_id', 'price_unit', 'product_id',
'move_dest_id', 'account_analytic_id')
def _make_key_for_grouping(self, order, fields):
"""From an order, return a tuple to be used as a key.
If two orders have the same key, they can be merged.
"""
key_list = []
for field in fields:
field_value = getattr(order, field)
if isinstance(field_value, browse_record):
field_value = field_value.id
elif isinstance(field_value, browse_null):
field_value = False
elif isinstance(field_value, list):
field_value = ((6, 0, tuple([v.id for v in field_value])),)
key_list.append((field, field_value))
key_list.sort()
return tuple(key_list)
def _can_merge(self, order):
"""Can the order be considered for merging with others?
This method can be surcharged in other modules.
"""
return order.state == 'draft'
def _initial_merged_order_data(self, order):
"""Build the initial values of a merged order."""
return {
'origin': order.origin,
'date_order': order.date_order,
'partner_id': order.partner_id.id,
'dest_address_id': order.dest_address_id.id,
'warehouse_id': order.warehouse_id.id,
'location_id': order.location_id.id,
'pricelist_id': order.pricelist_id.id,
'state': 'draft',
'order_line': {},
'notes': '%s' % (order.notes or '',),
'fiscal_position': (
order.fiscal_position and order.fiscal_position.id or False
),
}
def _update_merged_order_data(self, merged_data, order):
if order.date_order < merged_data['date_order']:
merged_data['date_order'] = order.date_order
if order.notes:
merged_data['notes'] = (
(merged_data['notes'] or '') + ('\n%s' % (order.notes,))
)
if order.origin:
if (
order.origin not in merged_data['origin'] and
merged_data['origin'] not in order.origin
):
merged_data['origin'] = (
(merged_data['origin'] or '') + ' ' + order.origin
)
return merged_data
def _group_orders(self, input_orders):
"""Return a dictionary where each element is in the form:
tuple_key: (dict_of_new_order_data, list_of_old_order_ids)
"""
key_fields = self._key_fields_for_grouping()
grouped_orders = {}
if len(input_orders) < 2:
return {}
for input_order in input_orders:
key = self._make_key_for_grouping(input_order, key_fields)
if key in grouped_orders:
grouped_orders[key] = (
self._update_merged_order_data(
grouped_orders[key][0],
input_order
),
grouped_orders[key][1] + [input_order.id]
)
else:
grouped_orders[key] = (
self._initial_merged_order_data(input_order),
[input_order.id]
)
grouped_order_data = grouped_orders[key][0]
for input_line in input_order.order_line:
line_key = self._make_key_for_grouping(
input_line,
self._key_fields_for_grouping_lines()
)
o_line = grouped_order_data['order_line'].setdefault(
line_key, {}
)
if o_line:
# merge the line with an existing line
o_line['product_qty'] += (
input_line.product_qty *
input_line.product_uom.factor /
o_line['uom_factor']
)
else:
# append a new "standalone" line
for field in ('product_qty', 'product_uom'):
field_val = getattr(input_line, field)
if isinstance(field_val, browse_record):
field_val = field_val.id
o_line[field] = field_val
o_line['uom_factor'] = (
input_line.product_uom.factor
if input_line.product_uom
else 1.0)
return self._cleanup_merged_line_data(grouped_orders)
def _cleanup_merged_line_data(self, grouped_orders):
"""Remove keys from merged lines, and merges of 1 order."""
result = {}
for order_key, (order_data, old_ids) in grouped_orders.iteritems():
if len(old_ids) > 1:
for key, value in order_data['order_line'].iteritems():
del value['uom_factor']
value.update(dict(key))
order_data['order_line'] = [
(0, 0, value)
for value in order_data['order_line'].itervalues()
]
result[order_key] = (order_data, old_ids)
return result
def _create_new_orders(self, cr, uid, grouped_orders, context=None):
"""Create the new merged orders in the database.
Return a dictionary that puts the created order ids in relation to the
original ones, in the form
new_order_id: [old_order_1_id, old_order_2_id]
"""
new_old_rel = {}
for key in grouped_orders:
new_order_data, old_order_ids = grouped_orders[key]
new_id = self.create(cr, uid, new_order_data, context=context)
new_old_rel[new_id] = old_order_ids
return new_old_rel
def _fix_workflow(self, cr, uid, new_old_rel):
"""Fix the workflow of the old and new orders.
Specifically, cancel the old ones and assign workflows to the new ones.
"""
wf_service = netsvc.LocalService("workflow")
for new_order_id in new_old_rel:
old_order_ids = new_old_rel[new_order_id]
for old_id in old_order_ids:
wf_service.trg_redirect(uid, 'purchase.order', old_id,
new_order_id, cr)
wf_service.trg_validate(uid, 'purchase.order', old_id,
'purchase_cancel', cr)
def do_merge(self, cr, uid, input_order_ids, context=None):
"""Merge Purchase Orders.
This method replaces the original one in the purchase module because
it did not provide any hooks for customization.
Receive a list of order ids, and return a dictionary where each
element is in the form:
new_order_id: [old_order_1_id, old_order_2_id]
New orders are created, and old orders are deleted.
"""
input_orders = self.browse(cr, uid, input_order_ids, context=context)
mergeable_orders = filter(self._can_merge, input_orders)
grouped_orders = self._group_orders(mergeable_orders)
new_old_rel = self._create_new_orders(cr, uid, grouped_orders,
context=context)
self._fix_workflow(cr, uid, new_old_rel)
return new_old_rel