47
55
if context is None:
50
# we need the step info
51
assert 'step' in context, 'Step not defined in context'
52
step = context['step']
54
58
pick_obj = self.pool.get('stock.picking')
55
59
res = super(create_picking, self).default_get(cr, uid, fields, context=context)
56
60
picking_ids = context.get('active_ids', [])
57
61
if not picking_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})
65
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
66
pick_type = self.get_picking_type(cr, uid, pick, context=context)
67
for m in pick.move_lines:
68
if m.state in ('done', 'cancel'):
70
result.append(self.__create_partial_picking_memory(m, pick_type))
72
if 'product_moves_in' in fields:
73
res.update({'product_moves_in': result})
74
if 'product_moves_out' in fields:
75
res.update({'product_moves_out': result})
84
76
if 'date' in fields:
85
77
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
80
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
81
result = super(create_picking, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
162
83
pick_obj = self.pool.get('stock.picking')
163
84
picking_ids = context.get('active_ids', False)
164
assert 'step' in context, 'No step defined in context'
165
step = context['step']
167
86
if not picking_ids:
168
87
# not called through an action (e.g. buildbot), return the default.
171
# get picking subtype
172
90
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'
91
picking_type = self.get_picking_type(cr, uid, pick, context=context)
186
93
_moves_arch_lst = """<form string="%s">
187
94
<field name="date" invisible="1"/>
188
95
<separator colspan="4" string="%s"/>
189
<field name="product_moves_%s" colspan="4" nolabel="1" mode="tree,form"></field>
190
""" % (_('Process Document'), _('Products'), field)
96
<field name="%s" colspan="4" nolabel="1" mode="tree,form" width="550" height="200" ></field>
97
""" % (_('Process Document'), _('Products'), "product_moves_" + picking_type)
191
98
_moves_fields = result['fields']
193
100
# add field related to picking type only
194
101
_moves_fields.update({
195
'product_moves_' + field: {'relation': 'stock.move.memory.' + field, 'type' : 'one2many', 'string' : 'Product Moves'},
102
'product_moves_' + picking_type: {'relation': 'stock.move.memory.'+picking_type, '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
105
_moves_arch_lst += """
219
106
<separator string="" colspan="4" />
220
107
<label string="" colspan="2"/>
221
<group col="4" colspan="2">
108
<group col="2" colspan="2">
222
109
<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"
111
<button name="do_create_picking" string="Create Picking"
239
112
colspan="1" type="object" icon="gtk-go-forward" />
243
115
result['arch'] = _moves_arch_lst
244
116
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)
119
def __create_partial_picking_memory(self, move, pick_type):
121
'product_id' : move.product_id.id,
122
'quantity' : move.product_qty,
123
'product_uom' : move.product_uom.id,
124
'prodlot_id' : move.prodlot_id.id,
126
'asset_id': move.asset_id.id,
129
if pick_type == 'in':
131
'cost' : move.product_id.standard_price,
132
'currency' : move.product_id.company_id.currency_id.id,
389
136
def do_create_picking(self, cr, uid, ids, context=None):
394
141
- transform data from wizard
397
144
assert context, 'no context, method call is wrong'
398
145
assert 'active_ids' in context, 'No picking ids in context. Action call is wrong'
147
pick_obj = self.pool.get('stock.picking')
400
148
picking_ids = context['active_ids']
401
# partial data from wizard
402
149
partial = self.browse(cr, uid, ids[0], context=context)
151
'delivery_date' : partial.date
404
pick_obj = self.pool.get('stock.picking')
405
move_obj = self.pool.get('stock.move')
154
for pick in pick_obj.browse(cr, uid, picking_ids, context=context):
155
moves_list = partial.product_moves_out
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,
157
for move in moves_list:
158
partial_datas.setdefault('move%s' % (move.move_id.id), []).append({'product_id': move.product_id.id,
420
159
'product_qty': move.quantity,
421
160
'product_uom': move.product_uom.id,
422
161
'prodlot_id': move.prodlot_id.id,
423
162
'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,
165
# create the new picking object
166
# TODO origin is not copied if name is not supplied as default.
167
# create a new sequence for each draft picking ticket, and bricoler with draft ref or something for traceability
168
new_pick_id = pick_obj.copy(cr, uid, picking_ids[0], {'move_lines': []}, context=context)
169
# create stock moves corresponding to partial datas
170
# for now, each new line from the wizard corresponds to a new stock.move
171
# it could be interesting to regroup according to production lot/asset id
172
for move in partial_datas:
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'
174
# display newly created picking ticket
176
'name':_("Picking Ticket"),
180
'res_model': 'stock.picking',
181
'res_id': new_pick_id,
182
'type': 'ir.actions.act_window',
186
def do_partial(self, cr, uid, ids, context=None):
187
""" Makes partial moves and pickings done.
188
@param self: The object pointer.
189
@param cr: A database cursor
190
@param uid: ID of the user currently logged in
191
@param fields: List of fields for which we want default values
192
@param context: A standard dictionary
193
@return: A dictionary which of fields with values.
518
195
pick_obj = self.pool.get('stock.picking')
519
move_obj = self.pool.get('stock.move')
520
# partial data from wizard
197
picking_ids = context.get('active_ids', False)
521
198
partial = self.browse(cr, uid, ids[0], context=context)
525
picking_ids = context['active_ids']
200
'delivery_date' : partial.date
526
203
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)
204
picking_type = self.get_picking_type(cr, uid, pick, context=context)
205
moves_list = picking_type == 'in' and partial.product_moves_in or partial.product_moves_out
207
for move in moves_list:
208
partial_datas['move%s' % (move.move_id.id)] = {
209
'product_id': move.id,
210
'product_qty': move.quantity,
211
'product_uom': move.product_uom.id,
212
'prodlot_id': move.prodlot_id.id,
214
if (picking_type == 'in') and (move.product_id.cost_method == 'average'):
215
partial_datas['move%s' % (move.move_id.id)].update({
216
'product_price' : move.cost,
217
'product_currency': move.currency.id,
219
pick_obj.do_partial(cr, uid, picking_ids, partial_datas, context=context)
220
return {'type': 'ir.actions.act_window_close'}
224
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: