1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) Copyright (C) 2011 MSF, TeMPO Consulting.
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU Affero General Public License as
9
# published by the Free Software Foundation, either version 3 of the
10
# License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Affero General Public License for more details.
17
# You should have received a copy of the GNU Affero General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
##############################################################################
22
from osv import fields, osv
23
from tools.translate import _
27
class create_picking(osv.osv_memory):
28
_name = "create.picking"
29
_description = "Create Picking"
31
'date': fields.datetime('Date', required=True),
32
'product_moves_picking' : fields.one2many('stock.move.memory.picking', 'wizard_id', 'Moves'),
33
'product_moves_ppl' : fields.one2many('stock.move.memory.ppl', 'wizard_id', 'Moves'),
34
'product_moves_families' : fields.one2many('stock.move.memory.families', 'wizard_id', 'Pack Families'),
35
'product_moves_returnproducts': fields.one2many('stock.move.memory.returnproducts', 'wizard_id', 'Return Products')
38
def default_get(self, cr, uid, fields, context=None):
39
""" To get default values for the object.
40
@param self: The object pointer.
41
@param cr: A database cursor
42
@param uid: ID of the user currently logged in
43
@param fields: List of fields for which we want default values
44
@param context: A standard dictionary
45
@return: A dictionary which of fields with values.
50
# we need the step info
51
assert 'step' in context, 'Step not defined in context'
52
step = context['step']
54
pick_obj = self.pool.get('stock.picking')
55
res = super(create_picking, self).default_get(cr, uid, fields, context=context)
56
picking_ids = context.get('active_ids', [])
61
if step in ('create', 'validate', 'ppl1', 'returnproducts'):
62
# memory moves wizards
63
# data generated from stock.moves
64
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
65
result.extend(self.__create_partial_picking_memory(pick, context=context))
66
elif step in ('ppl2'):
67
# pack families wizard
68
# data generated from previous wizard data
69
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
70
result.extend(self.__create_pack_families_memory(pick, context=context))
72
if 'product_moves_picking' in fields and step in ('create', 'validate'):
73
res.update({'product_moves_picking': result})
75
if 'product_moves_ppl' in fields and step in ('ppl1'):
76
res.update({'product_moves_ppl': result})
78
if 'product_moves_returnproducts' in fields and step in ('returnproducts'):
79
res.update({'product_moves_returnproducts': result})
81
if 'product_moves_families' in fields and step in ('ppl2'):
82
res.update({'product_moves_families': result})
85
res.update({'date': time.strftime('%Y-%m-%d %H:%M:%S')})
89
def __create_partial_picking_memory(self, pick, context=None):
91
generates the memory objects data depending on wizard step
93
- wizard_id seems to be filled automatically
95
assert context, 'No context defined'
96
assert 'step' in context, 'No step defined in context'
97
step = context['step']
99
# list for the current pick object
101
for move in pick.move_lines:
102
if move.state in ('done', 'cancel'):
105
'product_id' : move.product_id.id,
106
'asset_id': move.asset_id.id,
107
'quantity' : move.product_qty,
108
'product_uom' : move.product_uom.id,
109
'prodlot_id' : move.prodlot_id.id,
111
# specific management rules
112
'expiry_date': move.expired_date,
115
# the first wizard of ppl, we set default values as everything is packed in one pack
117
# move_memory.update({'qty_per_pack': move.product_qty, 'from_pack': 1, 'to_pack': 1})
118
# append the created dict
119
result.append(move_memory)
121
# return the list of dictionaries
124
def __create_pack_families_memory(self, pick, context=None):
126
generates the memory objects data depending on wizard step
128
- wizard_id seems to be filled automatically
130
assert context, 'No context defined'
131
assert 'step' in context, 'No step defined in context'
132
step = context['step']
133
assert 'partial_datas_ppl1' in context, 'No partial data from step1'
134
partial_datas_ppl1 = context['partial_datas_ppl1']
136
# list for the current pick object
138
from_packs = partial_datas_ppl1[pick.id].keys()
139
# we want the lines sorted in from_pack order
141
for from_pack in from_packs:
142
for to_pack in partial_datas_ppl1[pick.id][from_pack]:
144
'from_pack': from_pack,
147
# append the created dict
148
result.append(family_memory)
150
# return the list of dictionaries
153
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
155
generates the xml view
158
assert context, 'No context defined'
160
result = super(create_picking, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
162
pick_obj = self.pool.get('stock.picking')
163
picking_ids = context.get('active_ids', False)
164
assert 'step' in context, 'No step defined in context'
165
step = context['step']
168
# not called through an action (e.g. buildbot), return the default.
171
# get picking subtype
172
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
173
picking_subtype = pick.subtype
175
# select field to display
176
if picking_subtype == 'picking':
178
elif picking_subtype == 'ppl':
183
elif step == 'returnproducts':
184
field = 'returnproducts'
186
_moves_arch_lst = """<form string="%s">
187
<field name="date" invisible="1"/>
188
<separator colspan="4" string="%s"/>
189
<field name="product_moves_%s" colspan="4" nolabel="1" mode="tree,form"></field>
190
""" % (_('Process Document'), _('Products'), field)
191
_moves_fields = result['fields']
193
# add field related to picking type only
194
_moves_fields.update({
195
'product_moves_' + field: {'relation': 'stock.move.memory.' + field, 'type' : 'one2many', 'string' : 'Product Moves'},
198
# specify the button according to the screen
199
# picking, two wizard steps
200
# refactoring is needed here !
201
if picking_subtype == 'picking':
203
button = ('do_create_picking', 'Create Picking')
204
elif step == 'validate':
205
button = ('do_validate_picking', 'Validate Picking')
206
# ppl, two wizard steps
207
elif picking_subtype == 'ppl':
209
button = ('do_ppl1', 'Next')
211
button = ('do_ppl2', 'Validate PPL')
212
if step == 'returnproducts':
213
button = ('do_return_products', 'Return')
216
button = ('undefined', 'Undefined')
218
_moves_arch_lst += """
219
<separator string="" colspan="4" />
220
<label string="" colspan="2"/>
221
<group col="4" colspan="2">
222
<button icon='gtk-cancel' special="cancel"
223
string="_Cancel" />"""
226
_moves_arch_lst += """
227
<button name="back_ppl1" string="previous"
228
colspan="1" type="object" icon="gtk-go-back" />"""
230
elif step == 'returnproducts':
231
_moves_arch_lst += """
232
<button name="select_all" string="Select All"
233
colspan="1" type="object" icon="terp_stock_symbol-selection" />
234
<button name="deselect_all" string="Deselect All"
235
colspan="1" type="object" icon="terp_stock_symbol-selection" />"""
237
_moves_arch_lst += """
238
<button name="%s" string="%s"
239
colspan="1" type="object" icon="gtk-go-forward" />
243
result['arch'] = _moves_arch_lst
244
result['fields'] = _moves_fields
245
# add messages from specific management rules
246
result = self.pool.get('stock.partial.picking').add_message(cr, uid, result, context=context)
249
def select_all(self, cr, uid, ids, context=None):
251
select all buttons, write max qty in each line
253
for wiz in self.browse(cr, uid, ids, context=context):
254
for line in wiz.product_moves_picking:
255
# get the qty from the corresponding stock move
256
original_qty = line.move_id.product_qty
257
line.write({'quantity':original_qty,}, context=context)
258
for line in wiz.product_moves_returnproducts:
259
line.write({'qty_to_return':line.quantity,}, context=context)
262
'name': context.get('wizard_name'),
266
'res_model': context.get('model'),
268
'type': 'ir.actions.act_window',
275
def deselect_all(self, cr, uid, ids, context=None):
277
deselect all buttons, write 0 qty in each line
279
for wiz in self.browse(cr, uid, ids, context=context):
280
for line in wiz.product_moves_picking:
281
line.write({'quantity':0.0,}, context=context)
282
for line in wiz.product_moves_returnproducts:
283
line.write({'qty_to_return':0.0,}, context=context)
286
'name': context.get('wizard_name'),
290
'res_model': context.get('model'),
292
'type': 'ir.actions.act_window',
299
def generate_data_from_partial(self, cr, uid, ids, context=None):
301
data is located in product_moves_ppl
303
we generate the data structure from the first ppl wizard (ppl1)
306
{pick_id: {from_pack: {to_pack: {move_id: [{partial},]}}}}
308
data are indexed by pack_id, then by pack_family information (from_pack/to_pack)
309
and finally by move_id. Move_id indexing is added because within one
310
pack sequence we can find the same move_id multiple time thanks to split function.
312
with partial beeing the info for one stock.move.memory.ppl
315
assert context, 'no context, method call is wrong'
316
assert 'active_ids' in context, 'No picking ids in context. Action call is wrong'
318
pick_obj = self.pool.get('stock.picking')
319
# partial data from wizard
320
partial = self.browse(cr, uid, ids[0], context=context)
322
partial_datas_ppl1 = {}
325
picking_ids = context['active_ids']
326
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
328
partial_datas_ppl1[pick.id] = {}
330
memory_moves_list = partial.product_moves_ppl
331
# organize data according to from pack / to pack
332
for move in memory_moves_list:
333
partial_datas_ppl1[pick.id] \
334
.setdefault(move.from_pack, {}) \
335
.setdefault(move.to_pack, {}) \
336
.setdefault(move.move_id.id, []).append({
337
'product_id': move.product_id.id,
338
'product_qty': move.quantity,
339
'product_uom': move.product_uom.id,
340
'prodlot_id': move.prodlot_id.id,
341
'asset_id': move.asset_id.id,
342
'move_id': move.move_id.id,
343
'qty_per_pack': move.qty_per_pack,
344
'from_pack': move.from_pack,
345
'to_pack': move.to_pack,
348
return partial_datas_ppl1
350
def update_data_from_partial(self, cr, uid, ids, context=None):
352
update the list corresponding to moves for each sequence with ppl2 information
354
generated structure from step ppl1 wizard is updated with step ppl2 wizard,
355
the partial dictionaries are updated with pack_family related information
358
{pick_id: {from_pack: {to_pack: {move_id: [{partial},]}}}}
360
assert context, 'no context defined'
361
assert 'partial_datas_ppl1' in context, 'partial_datas_ppl1 not in context'
363
pick_obj = self.pool.get('stock.picking')
364
family_obj = self.pool.get('stock.move.memory.families')
365
# partial data from wizard
366
partial = self.browse(cr, uid, ids[0], context=context)
368
memory_families_list = partial.product_moves_families
370
partial_datas_ppl1 = context['partial_datas_ppl1']
373
picking_ids = context['active_ids']
374
for picking_id in picking_ids:
376
for from_pack in partial_datas_ppl1[picking_id]:
377
for to_pack in partial_datas_ppl1[picking_id][from_pack]:
378
# find corresponding sequence info
379
family_ids = family_obj.search(cr, uid, [('wizard_id', '=', ids[0]), ('from_pack', '=', from_pack), ('to_pack', '=', to_pack)], context=context)
380
# only one line should match
381
assert len(family_ids) == 1, 'No the good number of families : %i'%len(family_ids)
382
family = family_obj.read(cr, uid, family_ids, ['pack_type', 'length', 'width', 'height', 'weight'], context=context)[0]
385
for move in partial_datas_ppl1[picking_id][from_pack][to_pack]:
386
for partial in partial_datas_ppl1[picking_id][from_pack][to_pack][move]:
387
partial.update(family)
389
def do_create_picking(self, cr, uid, ids, context=None):
391
create the picking ticket from selected stock moves
392
-> only related to 'out' type stock.picking
394
- transform data from wizard
397
assert context, 'no context, method call is wrong'
398
assert 'active_ids' in context, 'No picking ids in context. Action call is wrong'
400
picking_ids = context['active_ids']
401
# partial data from wizard
402
partial = self.browse(cr, uid, ids[0], context=context)
404
pick_obj = self.pool.get('stock.picking')
405
move_obj = self.pool.get('stock.move')
410
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
412
partial_datas[pick.id] = {}
413
# out moves for delivery
414
memory_moves_list = partial.product_moves_picking
415
# organize data according to move id
416
for move in memory_moves_list:
417
# !!! only take into account if the quantity is greater than 0 !!!
419
partial_datas[pick.id].setdefault(move.move_id.id, []).append({'product_id': move.product_id.id,
420
'product_qty': move.quantity,
421
'product_uom': move.product_uom.id,
422
'prodlot_id': move.prodlot_id.id,
423
'asset_id': move.asset_id.id,
425
# call stock_picking method which returns action call
426
return pick_obj.do_create_picking(cr, uid, picking_ids, context=dict(context, partial_datas=partial_datas))
428
def quick_mode(self, cr, uid, ppl, context=None):
430
we do the quick mode, the ppl step is performed automatically
432
assert context, 'missing Context'
434
moves_ppl_obj = self.pool.get('stock.move.memory.ppl')
436
# set the corresponding ppl object
437
context['active_ids'] = [ppl.id]
440
context['step'] = 'ppl1'
441
# create a create_picking object for ppl1
442
wizard_ppl1 = self.create(cr, uid, {'date': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
443
# the default user values are used, they represent all packs in one pack sequence (pack family from:1, to:1)
444
# with a quantity per pack equal to the quantity
445
# these values are set in the create method of memory moves
448
# the wizard for ppl2 step is created here, the step is updated there also
449
wizard_dic = self.do_ppl1(cr, uid, [wizard_ppl1], context=context)
450
partial_datas_ppl1 = wizard_dic['context']['partial_datas_ppl1']
451
wizard_ppl2 = wizard_dic['res_id']
452
# the default user values are used, all the pack families values are set to zero, False (weight, height, ...)
455
self.do_ppl2(cr, uid, [wizard_ppl2], context=dict(context, partial_datas_ppl1=partial_datas_ppl1))
457
def do_validate_picking(self, cr, uid, ids, context=None):
459
create the picking ticket from selected stock moves
460
-> only related to 'out' type stock.picking
462
- transform data from wizard
465
assert context, 'no context, method call is wrong'
466
assert 'active_ids' in context, 'No picking ids in context. Action call is wrong'
468
picking_ids = context['active_ids']
469
# partial data from wizard
470
partial = self.browse(cr, uid, ids[0], context=context)
472
pick_obj = self.pool.get('stock.picking')
473
move_obj = self.pool.get('stock.move')
478
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
480
partial_datas[pick.id] = {}
481
# out moves for delivery
482
memory_moves_list = partial.product_moves_picking
483
# organize data according to move id
484
for move in memory_moves_list:
486
partial_datas[pick.id].setdefault(move.move_id.id, []).append({'product_id': move.product_id.id,
487
'product_qty': move.quantity,
488
'product_uom': move.product_uom.id,
489
'prodlot_id': move.prodlot_id.id,
490
'asset_id': move.asset_id.id,
493
# call stock_picking method which returns action call
494
return pick_obj.do_validate_picking(cr, uid, picking_ids, context=dict(context, partial_datas=partial_datas))
496
def integrity_check(self, cr, uid, ids, data, context=None):
498
integrity check on shipment data
500
for picking_data in data.values():
501
for move_data in picking_data.values():
502
if move_data.get('qty_to_return', False):
507
def do_return_products(self, cr, uid, ids, context=None):
509
process data and call do_return_products from stock picking
512
{picking_id: {move_id: {data}}}
515
assert context, 'no context, method call is wrong'
516
assert 'active_ids' in context, 'No picking ids in context. Action call is wrong'
518
pick_obj = self.pool.get('stock.picking')
519
move_obj = self.pool.get('stock.move')
520
# partial data from wizard
521
partial = self.browse(cr, uid, ids[0], context=context)
525
picking_ids = context['active_ids']
526
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
528
partial_datas[pick.id] = {}
529
# out moves for delivery
530
memory_moves_list = partial.product_moves_returnproducts
531
# organize data according to move id
532
for move in memory_moves_list:
533
if move.qty_to_return:
534
partial_datas[pick.id][move.move_id.id] = {'product_id': move.product_id.id,
535
'asset_id': move.asset_id.id,
536
'product_qty': move.quantity,
537
'product_uom': move.product_uom.id,
538
'prodlot_id': move.prodlot_id.id,
539
'qty_to_return': move.qty_to_return,
542
# integrity check on wizard data
543
if not self.integrity_check(cr, uid, ids, partial_datas, context=context):
544
raise osv.except_osv(_('Warning !'), _('You must select something to return!'))
546
return pick_obj.do_return_products(cr, uid, picking_ids, context=dict(context, partial_datas=partial_datas))
548
def do_ppl1(self, cr, uid, ids, context=None):
551
- call stock.picking>do_ppl1
554
assert context, 'no context, method call is wrong'
555
assert 'active_ids' in context, 'No picking ids in context. Action call is wrong'
557
pick_obj = self.pool.get('stock.picking')
559
picking_ids = context['active_ids']
560
# generate data structure
561
partial_datas_ppl1 = self.generate_data_from_partial(cr, uid, ids, context=context)
562
# call stock_picking method which returns action call
563
return pick_obj.do_ppl1(cr, uid, picking_ids, context=dict(context, partial_datas_ppl1=partial_datas_ppl1))
565
def back_ppl1(self, cr, uid, ids, context=None):
567
call back ppl1 step wizard
569
# we need the context for the wizard switch
570
assert context, 'no context defined'
572
wiz_obj = self.pool.get('wizard')
574
# no data for type 'back'
575
return wiz_obj.open_wizard(cr, uid, context['active_ids'], type='back', context=context)
577
def integrity_check_weight(self, cr, uid, ids, data, context=None):
579
integrity check on ppl2 data for weight
581
dict: {189L: {1: {1: {439: [{'asset_id': False, 'weight': False, 'product_id': 246, 'product_uom': 1,
582
'pack_type': False, 'length': False, 'to_pack': 1, 'height': False, 'from_pack': 1, 'prodlot_id': False,
583
'qty_per_pack': 1.0, 'product_qty': 1.0, 'width': False, 'move_id': 439}]}}}}
585
move_obj = self.pool.get('stock.move')
586
for picking_data in data.values():
587
for from_data in picking_data.values():
588
for to_data in from_data.values():
589
for move_data in to_data.values():
590
for data in move_data:
591
if not data.get('weight', False):
592
move = move_obj.browse(cr, uid, data.get('move_id'), context=context)
593
flow_type = move.picking_id.flow_type
594
if flow_type != 'quick':
599
def do_ppl2(self, cr, uid, ids, context=None):
601
- update partial_datas_ppl1
602
- call stock.picking>do_ppl2
605
assert context, 'no context, method call is wrong'
606
assert 'active_ids' in context, 'No picking ids in context. Action call is wrong'
608
pick_obj = self.pool.get('stock.picking')
610
picking_ids = context['active_ids']
611
# update data structure
612
self.update_data_from_partial(cr, uid, ids, context=context)
613
# integrity check on wizard data
614
partial_datas_ppl1 = context['partial_datas_ppl1']
615
if not self.integrity_check_weight(cr, uid, ids, partial_datas_ppl1, context=context):
616
raise osv.except_osv(_('Warning !'), _('You must specify a weight for each pack family!'))
617
# call stock_picking method which returns action call
618
return pick_obj.do_ppl2(cr, uid, picking_ids, context=context)