Merge lp:~jfb-tempo-consulting/unifield-server/US-5366 into lp:unifield-server

Proposed by jftempo on 2019-03-20
Status: Merged
Merged at revision: 5311
Proposed branch: lp:~jfb-tempo-consulting/unifield-server/US-5366
Merge into: lp:unifield-server
Diff against target: 1207 lines (+274/-565)
11 files modified
bin/addons/delivery_mechanism/delivery_mechanism.py (+11/-1)
bin/addons/kit/kit.py (+100/-104)
bin/addons/kit/kit_creation.py (+46/-83)
bin/addons/kit/wizard/substitute.py (+6/-3)
bin/addons/msf_cross_docking/cross_docking.py (+0/-22)
bin/addons/msf_outgoing/msf_outgoing.py (+0/-10)
bin/addons/return_claim/return_claim.py (+14/-7)
bin/addons/sale/sale_workflow.py (+0/-1)
bin/addons/stock/product.py (+9/-0)
bin/addons/stock/stock.py (+88/-83)
bin/addons/stock_override/stock.py (+0/-251)
To merge this branch: bzr merge lp:~jfb-tempo-consulting/unifield-server/US-5366
Reviewer Review Type Date Requested Status
UniField Reviewer Team 2019-03-20 Pending
Review via email: mp+364812@code.launchpad.net
To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/addons/delivery_mechanism/delivery_mechanism.py'
2--- bin/addons/delivery_mechanism/delivery_mechanism.py 2019-02-28 14:56:34 +0000
3+++ bin/addons/delivery_mechanism/delivery_mechanism.py 2019-03-20 14:43:51 +0000
4@@ -985,6 +985,7 @@
5 done_moves = [] # Moves that are completed
6 out_picks = set()
7 processed_out_moves = []
8+ processed_out_moves_by_exp = {}
9 track_changes_to_create = [] # list of dict that contains data on track changes to create at the method's end
10
11 picking_move_lines = move_obj.browse(cr, uid, picking_dict['move_lines'],
12@@ -1148,6 +1149,8 @@
13 # search for sol that match with the updated move:
14 move_obj.write(cr, uid, [out_move.id], move_values, context=context)
15 processed_out_moves.append(new_out_move_id)
16+ processed_out_moves_by_exp.setdefault(line.prodlot_id and line.prodlot_id.life_date or False, []).append(new_out_move_id)
17+
18 elif uom_partial_qty == out_move.product_qty and out_move.id not in processed_out_moves:
19 out_values.update({
20 'product_qty': remaining_out_qty,
21@@ -1157,6 +1160,7 @@
22 remaining_out_qty = 0.00
23 move_obj.write(cr, uid, [out_move.id], out_values, context=context)
24 processed_out_moves.append(out_move.id)
25+ processed_out_moves_by_exp.setdefault(line.prodlot_id and line.prodlot_id.life_date or False, []).append(out_move.id)
26 elif uom_partial_qty > out_move.product_qty and out_moves[out_moves.index(out_move)] != out_moves[-1] and out_move.id not in processed_out_moves:
27 # Just update the out move with the value of the out move with UoM of IN
28 out_qty = out_move.product_qty
29@@ -1171,6 +1175,7 @@
30 remaining_out_qty -= out_qty
31 move_obj.write(cr, uid, [out_move.id], out_values, context=context)
32 processed_out_moves.append(out_move.id)
33+ processed_out_moves_by_exp.setdefault(line.prodlot_id and line.prodlot_id.life_date or False, []).append(out_move.id)
34 else:
35 # Just update the data of the initial out move
36 processed_qty = lst_out_move is out_moves[-1] and uom_partial_qty - minus_qty or out_move.product_qty
37@@ -1184,9 +1189,11 @@
38 new_out_move_id = move_obj.copy(cr, uid, out_move.id, out_values, context=context)
39 context['keepLineNumber'] = False
40 processed_out_moves.append(new_out_move_id)
41+ processed_out_moves_by_exp.setdefault(line.prodlot_id and line.prodlot_id.life_date or False, []).append(new_out_move_id)
42 else:
43 move_obj.write(cr, uid, [out_move.id], out_values, context=context)
44 processed_out_moves.append(out_move.id)
45+ processed_out_moves_by_exp.setdefault(line.prodlot_id and line.prodlot_id.life_date or False, []).append(out_move.id)
46
47 if line.uom_id.id != out_move.product_uom.id:
48 uom_processed_qty = uom_obj._compute_qty(cr, uid, out_move.product_uom.id, processed_qty, line.uom_id.id)
49@@ -1437,7 +1444,10 @@
50 ))
51
52 if not sync_in and wizard.claim_type not in ('scrap', 'quarantine', 'return', 'missing'):
53- move_obj.action_assign(cr, uid, processed_out_moves)
54+ to_process = []
55+ for ed in sorted(processed_out_moves_by_exp.keys()):
56+ to_process += processed_out_moves_by_exp[ed]
57+ move_obj.action_assign(cr, uid, to_process)
58
59 # create track changes:
60 for tc_data in track_changes_to_create:
61
62=== modified file 'bin/addons/kit/kit.py'
63--- bin/addons/kit/kit.py 2018-09-18 13:46:35 +0000
64+++ bin/addons/kit/kit.py 2019-03-20 14:43:51 +0000
65@@ -1402,12 +1402,15 @@
66 #print res
67 return res
68
69- def _product_reserve_lot(self, cr, uid, ids, product_id, uom_id, context=None, lock=False):
70+ def _product_reserve_lot(self, cr, uid, ids, product_id, needed_qty, uom_id, context=None, lock=False):
71 """
72 refactoring of original reserver method, taking production lot into account
73
74 returning the original list-tuple structure + the total qty in each location
75 """
76+
77+ # TODO : how to deal with product attrbiutes changes (ie: from BN managed to not BN, from not BN to BN ?)
78+ # TODO : update compute_availability used in kit / clail
79 amount = 0.0
80 if context is None:
81 context = {}
82@@ -1418,109 +1421,102 @@
83 location_ids = self.search(cr, uid, [('location_id', 'child_of', ids)], context=context)
84 else:
85 location_ids = ids
86- # fefo list of lot
87- fefo_list = []
88- # data structure
89- data = {'fefo': fefo_list, 'total': 0.0}
90-
91- for id in location_ids:
92- # set up default value
93- data.setdefault(id, {}).setdefault('total', 0.0)
94- # lock the database if needed
95- if lock:
96- try:
97- # Must lock with a separate select query because FOR UPDATE can't be used with
98- # aggregation/group by's (when individual rows aren't identifiable).
99- # We use a SAVEPOINT to be able to rollback this part of the transaction without
100- # failing the whole transaction in case the LOCK cannot be acquired.
101- cr.execute("SAVEPOINT stock_location_product_reserve_lot")
102- cr.execute("""SELECT id FROM stock_move
103- WHERE product_id=%s AND
104- (
105- (location_dest_id=%s AND
106- location_id<>%s AND
107- state='done')
108- OR
109- (location_id=%s AND
110- location_dest_id<>%s AND
111- state in ('done', 'assigned'))
112- )
113- FOR UPDATE of stock_move NOWAIT""", (product_id, id, id, id, id), log_exceptions=False)
114- except Exception:
115- # Here it's likely that the FOR UPDATE NOWAIT failed to get the LOCK,
116- # so we ROLLBACK to the SAVEPOINT to restore the transaction to its earlier
117- # state, we return False as if the products were not available, and log it:
118- cr.execute("ROLLBACK TO stock_location_product_reserve_lot")
119- logger = logging.getLogger('stock.location')
120- logger.warn("Failed attempt to reserve product %s, likely due to another transaction already in progress. Next attempt is likely to work. Detailed error available at DEBUG level.", product_id)
121- logger.debug("Trace of the failed product reservation attempt: ", exc_info=True)
122- return False
123-
124- # SQL request is FEFO by default
125- # TODO merge different UOM directly in SQL statement
126- # example in class stock_report_prodlots_virtual(osv.osv): in report_stock_virtual.py
127- # class report_stock_inventory(osv.osv): in specific_rules.py
128- cr.execute("""
129- SELECT subs.product_uom, subs.prodlot_id, subs.expired_date, sum(subs.product_qty) AS product_qty FROM
130- (SELECT product_uom, prodlot_id, expired_date, sum(product_qty) AS product_qty
131- FROM stock_move
132- WHERE location_dest_id=%s AND
133- location_id<>%s AND
134- product_id=%s AND
135- state='done'
136- GROUP BY product_uom, prodlot_id, expired_date
137-
138- UNION
139-
140- SELECT product_uom, prodlot_id, expired_date, -sum(product_qty) AS product_qty
141- FROM stock_move
142- WHERE location_id=%s AND
143- location_dest_id<>%s AND
144- product_id=%s AND
145- state in ('done', 'assigned')
146- GROUP BY product_uom, prodlot_id, expired_date) as subs
147- GROUP BY product_uom, prodlot_id, expired_date
148- ORDER BY prodlot_id asc, expired_date asc
149- """,
150- (id, id, product_id, id, id, product_id))
151- results = cr.dictfetchall()
152- # merge results according to uom if needed
153- for r in results:
154- # consolidates the uom
155- amount = pool_uom._compute_qty(cr, uid, r['product_uom'], r['product_qty'], uom_id)
156- # total for all locations
157- total = data.setdefault('total', 0.0)
158- total += amount
159- data.update({'total': total})
160- # fill the data structure, total value for location
161- loc_tot = data.setdefault(id, {}).setdefault('total', 0.0)
162- loc_tot += amount
163- data.setdefault(id, {}).update({'total': loc_tot})
164- # production lot
165- lot_tot = data.setdefault(id, {}).setdefault(r['prodlot_id'], {}).setdefault('total', 0.0)
166- lot_tot += amount
167- data.setdefault(id, {}).setdefault(r['prodlot_id'], {}).update({'total': lot_tot, 'date': r['expired_date']})
168- # update the fefo list - will be sorted when all location has been treated - we can test only the last one, thanks to ORDER BY sql request
169- # only positive amount are taken into account
170- if r['prodlot_id']:
171- # FEFO logic is only meaningful if a production lot is associated
172- if fefo_list and fefo_list[-1]['location_id'] == id and fefo_list[-1]['prodlot_id'] == r['prodlot_id']:
173- # simply update the qty
174- if lot_tot > 0:
175- fefo_list[-1].update({'qty': lot_tot})
176- else:
177- fefo_list.pop(-1)
178- elif lot_tot > 0:
179- # append a new dic
180- fefo_list.append({'location_id': id,
181- 'uom_id': uom_id,
182- 'expired_date': r['expired_date'],
183- 'prodlot_id': r['prodlot_id'],
184- 'product_id': product_id,
185- 'qty': lot_tot})
186- # global FEFO sorting
187- data['fefo'] = sorted(fefo_list, cmp=lambda x, y: cmp(x.get('expired_date'), y.get('expired_date')), reverse=False)
188- return data
189+
190+ # lock the database if needed
191+ if lock:
192+ try:
193+ # Must lock with a separate select query because FOR UPDATE can't be used with
194+ # aggregation/group by's (when individual rows aren't identifiable).
195+ # We use a SAVEPOINT to be able to rollback this part of the transaction without
196+ # failing the whole transaction in case the LOCK cannot be acquired.
197+ cr.execute("SAVEPOINT stock_location_product_reserve_lot")
198+ cr.execute("""SELECT id FROM stock_move
199+ WHERE product_id=%s AND
200+ (
201+ (location_dest_id in %s AND
202+ location_id<>location_dest_id AND
203+ state='done')
204+ OR
205+ (location_id in %s AND
206+ location_dest_id<>location_id AND
207+ state in ('done', 'assigned'))
208+ )
209+ FOR UPDATE of stock_move NOWAIT""", (product_id, tuple(location_ids), tuple(location_ids)), log_exceptions=False)
210+ except Exception:
211+ # Here it's likely that the FOR UPDATE NOWAIT failed to get the LOCK,
212+ # so we ROLLBACK to the SAVEPOINT to restore the transaction to its earlier
213+ # state, we return False as if the products were not available, and log it:
214+ cr.execute("ROLLBACK TO stock_location_product_reserve_lot")
215+ logger = logging.getLogger('stock.location')
216+ logger.warn("Failed attempt to reserve product %s, likely due to another transaction already in progress. Next attempt is likely to work. Detailed error available at DEBUG level.", product_id)
217+ logger.debug("Trace of the failed product reservation attempt: ", exc_info=True)
218+ return {}
219+
220+ # SQL request is FEFO by default
221+ # TODO merge different UOM directly in SQL statement
222+ # example in class stock_report_prodlots_virtual(osv.osv): in report_stock_virtual.py
223+ # class report_stock_inventory(osv.osv): in specific_rules.py
224+
225+ factor = pool_uom.read(cr, uid, uom_id, ['factor'])['factor']
226+ cr.execute("""
227+ SELECT subs.location, subs.parent_left, subs.prodlot_id, subs.expired_date, sum(subs.product_qty) AS product_qty FROM
228+ (SELECT m.location_dest_id as location, loc.parent_left, m.prodlot_id, lot.life_date as expired_date, sum(m.product_qty / move_uom.factor) AS product_qty
229+ FROM stock_move m
230+ LEFT JOIN stock_production_lot lot ON lot.id = m.prodlot_id
231+ LEFT JOIN stock_location loc ON loc.id = m.location_dest_id
232+ LEFT JOIN product_uom move_uom ON move_uom.id = m.product_uom
233+ WHERE m.location_dest_id in %s AND
234+ m.location_id<>m.location_dest_id AND
235+ m.product_id=%s AND
236+ m.state='done' AND
237+ (expired_date is null or expired_date >= CURRENT_DATE)
238+ GROUP BY m.location_dest_id, loc.parent_left, m.prodlot_id, lot.life_date
239+
240+ UNION
241+
242+ SELECT m.location_id as location, loc.parent_left, m.prodlot_id, lot.life_date as expired_date, -sum(m.product_qty / move_uom.factor) AS product_qty
243+ FROM stock_move m
244+ LEFT JOIN stock_production_lot lot on lot.id = m.prodlot_id
245+ LEFT JOIN stock_location loc on loc.id = m.location_id
246+ LEFT JOIN product_uom move_uom ON move_uom.id = m.product_uom
247+ WHERE m.location_id in %s AND
248+ m.location_dest_id<>m.location_id AND
249+ m.product_id=%s AND
250+ m.state in ('done', 'assigned') AND
251+ (expired_date is null or expired_date >= CURRENT_DATE)
252+ GROUP BY m.location_id, loc.parent_left, m.prodlot_id, lot.life_date) as subs
253+ GROUP BY location, parent_left, prodlot_id, expired_date
254+ ORDER BY expired_date asc, prodlot_id asc, parent_left
255+ """,
256+ (tuple(location_ids), product_id, tuple(location_ids), product_id))
257+
258+ results = []
259+ for r in cr.dictfetchall():
260+ # consolidates the uom
261+ amount = r['product_qty'] * factor
262+ # total for all locations
263+ if amount <= 0:
264+ continue
265+ if amount >= needed_qty:
266+ if results and results[-1][1:4] == [r['location'], r['expired_date'], r['prodlot_id']]:
267+ results[-1][0] += needed_qty
268+ else:
269+ results.append([needed_qty, r['location'], r['expired_date'], r['prodlot_id']])
270+ return results
271+
272+ if results and results[-1][1:4] == [r['location'], r['expired_date'], r['prodlot_id']]:
273+ results[-1][0] += amount
274+ else:
275+ results.append([amount, r['location'], r['expired_date'], r['prodlot_id']])
276+
277+ needed_qty -= amount
278+
279+ if not results:
280+ return []
281+ if needed_qty:
282+ results.append([needed_qty, False, False, False, False])
283+
284+ return results
285
286 stock_location()
287
288
289=== modified file 'bin/addons/kit/kit_creation.py'
290--- bin/addons/kit/kit_creation.py 2018-10-30 10:56:14 +0000
291+++ bin/addons/kit/kit_creation.py 2019-03-20 14:43:51 +0000
292@@ -628,42 +628,50 @@
293
294 if data[product_id]['object'].perishable: # perishable for perishable or batch management
295 # the product is batch management we use the FEFO list
296- for loc in res['fefo']:
297+ for expired_date in res.get('fefo', []):
298+ if not needed_qty:
299+ break
300 # we ignore the batch that are outdated
301- expired_date = prodlot_obj.read(cr, uid, loc['prodlot_id'], ['life_date'], context)['life_date']
302 if datetime.strptime(expired_date, "%Y-%m-%d") < datetime.today():
303 continue
304- # as long all needed are not fulfilled
305- if needed_qty > 0.0:
306- # we treat the available qty from FEFO list corresponding to needed quantity
307- if loc['qty'] > needed_qty:
308- # we have everything !
309- selected_qty = needed_qty
310- needed_qty = 0.0
311- else:
312- # we take all available
313- selected_qty = loc['qty']
314- needed_qty -= selected_qty
315- # stock move values
316- values = {'kit_creation_id_stock_move': obj.id,
317- 'name': data[product_id]['object'].name,
318- 'picking_id': obj.internal_picking_id_kit_creation.id,
319- 'product_uom': uom_id,
320- 'product_id': product_id,
321- 'date_expected': context['common']['date'],
322- 'date': context['common']['date'],
323- 'product_qty': selected_qty,
324- 'prodlot_id': loc['prodlot_id'],
325- 'location_id': loc['location_id'],
326- 'location_dest_id': context['common']['kitting_id'],
327- 'state': 'assigned', # available
328- 'reason_type_id': context['common']['reason_type_id'],
329- 'to_consume_id_stock_move': data[product_id]['uoms'][uom_id]['to_consume_id'],
330- 'original_from_process_stock_move': original_flag,
331- }
332- move_obj.create(cr, uid, values, context=context)
333- # we reset original move flag
334- original_flag = False
335+
336+ for prod_id in res[expired_date]:
337+ if not needed_qty:
338+ break
339+ for loc_id in res[expired_date][prod_id]:
340+ if not needed_qty:
341+ break
342+ # as long all needed are not fulfilled
343+ if needed_qty > 0.0:
344+ # we treat the available qty from FEFO list corresponding to needed quantity
345+ if res[expired_date][prod_id][loc_id]['total'] > needed_qty:
346+ # we have everything !
347+ selected_qty = needed_qty
348+ needed_qty = 0.0
349+ else:
350+ # we take all available
351+ selected_qty = res[expired_date][prod_id][loc_id]['total']
352+ needed_qty -= selected_qty
353+ # stock move values
354+ values = {'kit_creation_id_stock_move': obj.id,
355+ 'name': data[product_id]['object'].name,
356+ 'picking_id': obj.internal_picking_id_kit_creation.id,
357+ 'product_uom': uom_id,
358+ 'product_id': product_id,
359+ 'date_expected': context['common']['date'],
360+ 'date': context['common']['date'],
361+ 'product_qty': selected_qty,
362+ 'prodlot_id': prod_id,
363+ 'location_id': loc_id,
364+ 'location_dest_id': context['common']['kitting_id'],
365+ 'state': 'assigned', # available
366+ 'reason_type_id': context['common']['reason_type_id'],
367+ 'to_consume_id_stock_move': data[product_id]['uoms'][uom_id]['to_consume_id'],
368+ 'original_from_process_stock_move': original_flag,
369+ }
370+ move_obj.create(cr, uid, values, context=context)
371+ # we reset original move flag
372+ original_flag = False
373 if needed_qty:
374 values = {'kit_creation_id_stock_move': obj.id,
375 'name': data[product_id]['object'].name,
376@@ -674,7 +682,7 @@
377 'date': context['common']['date'],
378 'product_qty': needed_qty,
379 'prodlot_id': False,
380- 'location_id': loc['location_id'],
381+ 'location_id': default_location_id,
382 'location_dest_id': context['common']['kitting_id'],
383 'state': 'confirmed', # not available
384 'reason_type_id': context['common']['reason_type_id'],
385@@ -687,18 +695,18 @@
386
387 else:
388 # the product is not batch management, we use locations in id order
389- for loc in sorted(res.keys()):
390- if isinstance(loc, int) and res[loc]['total'] > 0.0:
391+ for loc in sorted(res.get(None, {}).get(None, [])):
392+ if isinstance(loc, int) and res[None][None][loc]['total'] > 0.0:
393 # as long all needed are not fulfilled
394 if needed_qty > 0.0:
395 # we treat the available qty from locations corresponding to needed quantity
396- if res[loc]['total'] > needed_qty:
397+ if res[None][None][loc]['total'] > needed_qty:
398 # we have everything !
399 selected_qty = needed_qty
400 needed_qty = 0.0
401 else:
402 # we take all available
403- selected_qty = res[loc]['total']
404+ selected_qty = res[None][None][loc]['total']
405 needed_qty -= selected_qty
406 # stock move values
407 values = {'kit_creation_id_stock_move': obj.id,
408@@ -1392,51 +1400,6 @@
409 'target': 'crush',
410 }
411
412- def check_assign_lot(self, cr, uid, ids, context=None):
413- """
414- check the assignation of stock move taking into account lot and FEFO rule
415- """
416- # treated move ids
417- done = []
418- count = 0
419- pickings = {}
420- if context is None:
421- context = {}
422- for move in self.browse(cr, uid, ids, context=context):
423- if self._hook_check_assign(cr, uid, move=move):
424- # if move.product_id.type == 'consu' or move.location_id.usage == 'supplier':
425- if move.state in ('confirmed', 'waiting'):
426- done.append(move.id)
427- pickings[move.picking_id.id] = 1
428- continue
429- if move.state in ('confirmed', 'waiting'):
430- # Important: we must pass lock=True to _product_reserve() to avoid race conditions and double reservations
431- res = self.pool.get('stock.location')._product_reserve(cr, uid, [move.location_id.id], move.product_id.id, move.product_qty, {'uom': move.product_uom.id}, lock=True)
432- if res:
433- #_product_available_test depends on the next status for correct functioning
434- #the test does not work correctly if the same product occurs multiple times
435- #in the same order. This is e.g. the case when using the button 'split in two' of
436- #the stock outgoing form
437- self.write(cr, uid, [move.id], {'state':'assigned'})
438- done.append(move.id)
439- pickings[move.picking_id.id] = 1
440- r = res.pop(0)
441- cr.execute('update stock_move set location_id=%s, product_qty=%s, product_uos_qty=%s where id=%s', (r[1], r[0], r[0] * move.product_id.uos_coeff, move.id))
442-
443- while res:
444- r = res.pop(0)
445- move_id = self.copy(cr, uid, move.id, {'product_qty': r[0],'product_uos_qty': r[0] * move.product_id.uos_coeff,'location_id': r[1]})
446- done.append(move_id)
447- if done:
448- count += len(done)
449- self.write(cr, uid, done, {'state': 'assigned'})
450-
451- if count:
452- for pick_id in pickings:
453- wf_service = netsvc.LocalService("workflow")
454- wf_service.trg_write(uid, 'stock.picking', pick_id, cr)
455- return count
456-
457 def unlink(self, cr, uid, ids, context=None, force=False):
458 '''
459 override the function so we prevent deletion of original_from_process_stock_move stock.moves
460
461=== modified file 'bin/addons/kit/wizard/substitute.py'
462--- bin/addons/kit/wizard/substitute.py 2018-09-18 13:46:35 +0000
463+++ bin/addons/kit/wizard/substitute.py 2019-03-20 14:43:51 +0000
464@@ -623,11 +623,14 @@
465 # we check for the available qty (in:done, out: assigned, done) - consider_child_locations=False
466 res = loc_obj.compute_availability(cr, uid, [location_id], False, product_id, uom_id, context=context)
467 if prodlot_id:
468- # if a lot is specified, we take this specific qty info - the lot may not be available in this specific location
469- qty = res[location_id].get(prodlot_id, False) and res[location_id][prodlot_id]['total'] or 0.0
470+ qty = 0
471+ for x in res.get('fefo'):
472+ if res.get(x, {}).get(prodlot_id, {}).get(location_id):
473+ qty = res[x][prodlot_id][location_id].get('total')
474+ break
475 else:
476 # otherwise we take total according to the location
477- qty = res[location_id]['total']
478+ qty = res.get(None, {}).get(None, {}).get(location_id, {}).get('total', 0)
479 # update the result
480 result.setdefault('value', {}).update({'qty_substitute_item': qty,
481 'uom_id_substitute_item': uom_id,
482
483=== modified file 'bin/addons/msf_cross_docking/cross_docking.py'
484--- bin/addons/msf_cross_docking/cross_docking.py 2018-09-24 15:31:03 +0000
485+++ bin/addons/msf_cross_docking/cross_docking.py 2019-03-20 14:43:51 +0000
486@@ -638,17 +638,6 @@
487 todo = new_todo
488 # we rechech availability
489 self.action_assign(cr, uid, todo, context)
490- #FEFO
491- self.fefo_update(cr, uid, todo, context)
492- # below we cancel availability to recheck it
493-# stock_picking_id = self.read(cr, uid, todo, ['picking_id'], context=context)[0]['picking_id'][0]
494-# picking_todo.append(stock_picking_id)
495-# # we cancel availability
496-# self.pool.get('stock.picking').cancel_assign(cr, uid, [stock_picking_id])
497-# # we recheck availability
498-# self.pool.get('stock.picking').action_assign(cr, uid, [stock_picking_id])
499-# if picking_todo:
500-# self.pool.get('stock.picking').check_all_move_cross_docking(cr, uid, picking_todo, context=context)
501 return ret
502
503 @check_cp_rw
504@@ -691,17 +680,6 @@
505 todo = new_todo
506 # we rechech availability
507 self.action_assign(cr, uid, todo)
508-
509- #FEFO
510- self.fefo_update(cr, uid, todo, context)
511-# stock_picking_id = self.read(cr, uid, todo, ['picking_id'], context=context)[0]['picking_id'][0]
512-# picking_todo.append(stock_picking_id)
513- # we cancel availability
514-# self.pool.get('stock.picking').cancel_assign(cr, uid, [stock_picking_id])
515- # we recheck availability
516-# self.pool.get('stock.picking').action_assign(cr, uid, [stock_picking_id])
517-# if picking_todo:
518-# self.pool.get('stock.picking').check_all_move_cross_docking(cr, uid, picking_todo, context=context)
519 return True
520
521 stock_move()
522
523=== modified file 'bin/addons/msf_outgoing/msf_outgoing.py'
524--- bin/addons/msf_outgoing/msf_outgoing.py 2019-02-12 08:51:03 +0000
525+++ bin/addons/msf_outgoing/msf_outgoing.py 2019-03-20 14:43:51 +0000
526@@ -3079,16 +3079,6 @@
527
528 return new_packing_id
529
530- def _hook_action_assign_raise_exception(self, cr, uid, ids, context=None, *args, **kwargs):
531- '''
532- Please copy this to your module's method also.
533- This hook belongs to the action_assign method from stock>stock.py>stock_picking class
534-
535- - allow to choose wether or not an exception should be raised in case of no stock move
536- '''
537- res = super(stock_picking, self)._hook_action_assign_raise_exception(cr, uid, ids, context=context, *args, **kwargs)
538- return res and False
539-
540 def _hook_log_picking_modify_message(self, cr, uid, ids, context=None, *args, **kwargs):
541 '''
542 stock>stock.py>log_picking
543
544=== modified file 'bin/addons/return_claim/return_claim.py'
545--- bin/addons/return_claim/return_claim.py 2018-10-16 10:29:04 +0000
546+++ bin/addons/return_claim/return_claim.py 2019-03-20 14:43:51 +0000
547@@ -2212,11 +2212,14 @@
548 uom_id = uom_id or product_obj.uom_id.id
549 res = loc_obj.compute_availability(cr, uid, [location_id], False, product_id, uom_id, context=context)
550 if prodlot_id:
551- # if a lot is specified, we take this specific qty info - the lot may not be available in this specific location
552- qty = res[location_id].get(prodlot_id, False) and res[location_id][prodlot_id]['total'] or 0.0
553+ qty = 0
554+ for x in res.get('fefo'):
555+ if res.get(x, {}).get(prodlot_id, {}).get(location_id):
556+ qty = res[x][prodlot_id][location_id].get('total')
557+ break
558 else:
559 # otherwise we take total according to the location
560- qty = res[location_id]['total']
561+ qty = res.get(None, {}).get(None, {}).get(location_id, {}).get('total', 0)
562 # update the result
563 result.setdefault('value', {}).update({'qty_claim_product_line': qty,
564 'uom_id_claim_product_line': uom_id,
565@@ -2355,11 +2358,15 @@
566 prodlot_id = obj.lot_id_claim_product_line.id
567 # if the product has a production lot and the production lot exist in the specified location, we take corresponding stock
568 available_qty = 0.0
569- if prodlot_id and prodlot_id in data[location_id]:
570- available_qty = data[location_id][prodlot_id]['total']
571+
572+ if prodlot_id:
573+ for x in data.get('fefo'):
574+ if data.get(x, {}).data(prodlot_id, {}).data(location_id):
575+ available_qty = data[x][prodlot_id][location_id].get('total')
576+ break
577 else:
578- # otherwise we take the total quantity for the selected location - no lot for this product
579- available_qty = data[location_id]['total']
580+ # otherwise we take total according to the location
581+ available_qty = data.get(None, {}).get(None, {}).get(location_id, {}).get('total', 0)
582 result[obj.id].update({'hidden_stock_available_claim_product_line': available_qty})
583
584 return result
585
586=== modified file 'bin/addons/sale/sale_workflow.py'
587--- bin/addons/sale/sale_workflow.py 2019-02-23 17:04:01 +0000
588+++ bin/addons/sale/sale_workflow.py 2019-03-20 14:43:51 +0000
589@@ -514,7 +514,6 @@
590 # run check availability on PICK/OUT:
591 if picking_data['type'] == 'out' and picking_data['subtype'] in ['picking', 'standard']:
592 self.pool.get('stock.move').action_assign(cr, uid, [move_id])
593- self.pool.get('stock.move').fefo_update(cr, uid, [move_id], context=context)
594 # self.pool.get('stock.picking').action_assign(cr, uid, [pick_to_use], context=context)
595 if picking_data['type'] == 'internal' and sol.type == 'make_to_stock' and sol.order_id.procurement_request:
596 wf_service.trg_validate(uid, 'stock.picking', pick_to_use, 'button_confirm', cr)
597
598=== modified file 'bin/addons/stock/product.py'
599--- bin/addons/stock/product.py 2019-01-28 15:49:22 +0000
600+++ bin/addons/stock/product.py 2019-03-20 14:43:51 +0000
601@@ -264,6 +264,9 @@
602 prodlot_id_str = (prodlot_id and (' AND prodlot_id = %s ' % str(prodlot_id)) or '')
603 date_str = date_str and ' AND %s '% date_str or ''
604 if 'in' in what:
605+ if not states and context.get('in_states'):
606+ where[3] = tuple(context['in_states'])
607+
608 # all moves from a location out of the set to a location in the set
609 cr.execute("""
610 select sum(product_qty), product_id, product_uom
611@@ -275,6 +278,8 @@
612 group by product_id,product_uom""" % (prodlot_id_str, date_str),tuple(where)) # not_a_user_entry
613 results = cr.fetchall()
614 if 'out' in what:
615+ if not states and context.get('out_states'):
616+ where[3] = tuple(context['out_states'])
617 # all moves from a location in the set to a location out of the set
618 cr.execute("""
619 select sum(product_qty), product_id, product_uom
620@@ -336,6 +341,9 @@
621 c.update({ 'states': ('confirmed','waiting','assigned'), 'what': ('in',) })
622 elif f == 'outgoing_qty':
623 c.update({ 'states': ('confirmed','waiting','assigned'), 'what': ('out',) })
624+ elif f == 'qty_allocable':
625+ c.updates({'what': ('in', 'out'), 'in_states': ('done',), 'out_states': ('done', 'assigned')})
626+
627 stock = self.get_product_available(cr, uid, ids, context=c)
628 if any(stock.values()):
629 for id in ids:
630@@ -343,6 +351,7 @@
631 return res
632
633 _columns = {
634+ 'qty_allocable': fields.function(_product_available, method=True, type='float', string='Available Qty', help="Real stock - reserved stock", multi='qty_available', digits_compute=dp.get_precision('Product UoM'), related_uom='uom_id'),
635 'qty_available': fields.function(_product_available, method=True, type='float', string='Real Stock', help="Current quantities of products in selected locations or all internal if none have been selected.", multi='qty_available', digits_compute=dp.get_precision('Product UoM'), related_uom='uom_id'),
636 'virtual_available': fields.function(_product_available, method=True, type='float', string='Virtual Stock', help="Future stock for this product according to the selected locations or all internal if none have been selected. Computed as: Real Stock - Outgoing + Incoming.", multi='qty_available', digits_compute=dp.get_precision('Product UoM'), related_uom='uom_id'),
637 'incoming_qty': fields.function(_product_available, method=True, type='float', string='Incoming', help="Quantities of products that are planned to arrive in selected locations or all internal if none have been selected.", multi='qty_available', digits_compute=dp.get_precision('Product UoM'), related_uom='uom_id'),
638
639=== modified file 'bin/addons/stock/stock.py'
640--- bin/addons/stock/stock.py 2018-11-30 08:53:00 +0000
641+++ bin/addons/stock/stock.py 2019-03-20 14:43:51 +0000
642@@ -372,7 +372,6 @@
643 reach the requested product_qty (``qty`` is expressed in the default uom of the product), of False if enough
644 products could not be found, or the lock could not be obtained (and ``lock`` was True).
645 """
646- result = []
647 amount = 0.0
648 if context is None:
649 context = {}
650@@ -383,6 +382,7 @@
651 temp.remove(location_dest_id)
652 temp.append(location_dest_id)
653
654+ result_qty = []
655 for id in temp:
656 if lock:
657 try:
658@@ -435,25 +435,31 @@
659 """,
660 (id, id, product_id))
661 results += cr.dictfetchall()
662- total = 0.0
663- results2 = 0.0
664+ total_loc = 0.0
665
666 for r in results:
667 amount = pool_uom._compute_qty(cr, uid, r['product_uom'], r['product_qty'], context.get('uom', False))
668- results2 += amount
669- total += amount
670+ total_loc += amount
671
672- if total <= 0.0:
673+ if total_loc <= 0.0:
674 continue
675
676- amount = results2
677- if amount > 0:
678- if amount > min(total, product_qty):
679- amount = min(product_qty, total)
680-
681- return self._hook_proct_reserve(cr,uid,product_qty,result,amount, id, ids)
682-
683- return False
684+ if product_qty <= total_loc:
685+ result_qty.append((product_qty, id))
686+ return result_qty
687+
688+ result_qty.append((total_loc, id))
689+ product_qty -= total_loc
690+
691+ if not result_qty:
692+ # zero available stock
693+ return []
694+
695+ if product_qty:
696+ # remaining not available qty
697+ result_qty.append((product_qty, False))
698+
699+ return result_qty
700
701
702
703@@ -802,29 +808,6 @@
704 # TODO: Check locations to see if in the same location ?
705 return True
706
707- def _hook_action_assign_raise_exception(self, cr, uid, ids, context=None, *args, **kwargs):
708- '''
709- Please copy this to your module's method also.
710- This hook belongs to the action_assign method from stock>stock.py>stock_picking class
711-
712- - allow to choose wether or not an exception should be raised in case of no stock move
713- '''
714- return True
715-
716- def _hook_get_move_ids(self, cr, uid, *args, **kwargs):
717- pick = kwargs['pick']
718- return [x.id for x in pick.move_lines if x.state in ('waiting', 'confirmed')]
719-
720- def _hook_action_assign_batch(self, cr, uid, ids, context=None):
721- '''
722- Please copy this to your module's method also.
723- This hook belongs to the action_assign method from stock>stock.py>stock_picking class
724-
725- - when product is Expiry date mandatory, a "pre-assignment" of batch numbers regarding the available quantity
726- and location logic in addition to FEFO logic (First expired first out).
727- '''
728- return True
729-
730 def action_assign(self, cr, uid, ids, context=None, *args):
731 """ Changes state of picking to available if all moves are confirmed.
732 @return: True
733@@ -834,13 +817,13 @@
734 if context is None:
735 context = {}
736 move_obj = self.pool.get('stock.move')
737- for pick in self.browse(cr, uid, ids):
738- move_ids = self._hook_get_move_ids(cr, uid, pick=pick)
739- if not move_ids:
740- if self._hook_action_assign_raise_exception(cr, uid, ids, context=context,):
741- raise osv.except_osv(_('Warning !'),_('Not enough stock, unable to reserve the products.'))
742+ for pick in self.read(cr, uid, ids, ['name']):
743+ move_ids = move_obj.search(cr, uid, [('picking_id', '=', pick['id']),
744+ ('state', 'in', ('waiting', 'confirmed'))], order='prodlot_id, product_qty desc')
745 move_obj.action_assign(cr, uid, move_ids)
746- self._hook_action_assign_batch(cr, uid, ids, context=context)
747+ self.infolog(cr, uid, 'Check availability ran on stock.picking id:%s (%s)' % (
748+ pick['id'], pick['name'],
749+ ))
750 return True
751
752 def force_assign(self, cr, uid, ids, *args):
753@@ -2431,26 +2414,25 @@
754 if isinstance(ids, (int, long)):
755 ids = [ids]
756
757- vals.update({'state': 'confirmed'})
758+ # check qty > 0 or raise
759+ self.check_product_quantity(cr, uid, ids, context=context)
760+
761+ vals.update({'state': 'confirmed', 'already_confirmed': True})
762 self.write(cr, uid, ids, vals)
763 self.prepare_action_confirm(cr, uid, ids, context=context)
764 return []
765
766- def _hook_confirmed_move(self, cr, uid, *args, **kwargs):
767- '''
768- Always return True
769- '''
770- return True
771
772 def action_assign(self, cr, uid, ids, *args):
773 """ Changes state to confirmed or waiting.
774 @return: List of values
775 """
776 todo = []
777- for move in self.read(cr, uid, ids, ['state', 'already_confirmed']):
778- self._hook_confirmed_move(cr, uid, already_confirmed=move['already_confirmed'], move_id=move['id'])
779- if move['state'] in ('confirmed', 'waiting'):
780- todo.append(move['id'])
781+ for move in self.browse(cr, uid, ids, fields_to_fetch=['state', 'already_confirmed']):
782+ if not move.already_confirmed:
783+ self.action_confirm(cr, uid, [move.id])
784+ if move.state in ('confirmed', 'waiting'):
785+ todo.append(move.id)
786 res = self.check_assign(cr, uid, todo)
787 return res
788
789@@ -2478,13 +2460,6 @@
790 self.write(cr, uid, ids, {'state': 'confirmed'})
791 return True
792
793- def _hook_check_assign(self, cr, uid, *args, **kwargs):
794- '''
795- kwargs['move'] is the current move
796- '''
797- move = kwargs['move']
798- return move.product_id.type == 'consu' or move.location_id.usage == 'supplier'
799-
800 #
801 # Duplicate stock.move
802 #
803@@ -2493,40 +2468,70 @@
804 @return: No. of moves done
805 """
806 done = []
807- notdone = []
808+ move_to_assign = []
809 count = 0
810 pickings = {}
811 if context is None:
812 context = {}
813- move_to_assign = set()
814+
815 for move in self.browse(cr, uid, ids, context=context):
816- if self._hook_check_assign(cr, uid, move=move):
817- # if move.product_id.type == 'consu' or move.location_id.usage == 'supplier':
818+ if move.location_id.usage == 'supplier' or (move.location_id.usage == 'customer' and move.location_id.location_category == 'consumption_unit'):
819 if move.state in ('confirmed', 'waiting'):
820- done.append(move.id)
821- pickings[move.picking_id.id] = 1
822+ if move.location_id.id == move.location_dest_id.id:
823+ done.append(move.id)
824+ else:
825+ move_to_assign.append(move.id)
826+ pickings.setdefault(move.picking_id.id, 0)
827+ pickings[move.picking_id.id] += 1
828 continue
829 if move.state in ('confirmed', 'waiting'):
830+ bn_needed = move.product_id.perishable
831 # Important: we must pass lock=True to _product_reserve() to avoid race conditions and double reservations
832- res = self.pool.get('stock.location')._product_reserve(cr, uid, [move.location_id.id], move.product_id.id, move.product_qty, move.location_dest_id.id ,{'uom': move.product_uom.id}, lock=True)
833+ res = self.pool.get('stock.location')._product_reserve_lot(cr, uid, [move.location_id.id], move.product_id.id, move.product_qty, move.product_uom.id, lock=True)
834 if res:
835- # _product_available_test depends on the next status for correct functioning
836- # the test does not work correctly if the same product occurs multiple times
837- # in the same order. This is e.g. the case when using the button 'split in two' of
838- # the stock outgoing form
839- move_to_assign.add(move.id)
840- done.append(move.id)
841- pickings[move.picking_id.id] = 1
842+ if move.location_id.id == move.location_dest_id.id:
843+ state = 'done'
844+ done.append(move.id)
845+ else:
846+ state = 'assigned'
847+ move_to_assign.append(move.id)
848+
849+ pickings.setdefault(move.picking_id.id, 0)
850+ pickings[move.picking_id.id] += 1
851 r = res.pop(0)
852- cr.execute('update stock_move set location_id=%s, product_qty=%s, product_uos_qty=%s where id=%s', (r[1], r[0], r[0] * move.product_id.uos_coeff, move.id))
853-
854- done, notdone = self._hook_copy_stock_move(cr, uid, res, move, done, notdone)
855- count = self._hook_write_state_stock_move(cr, uid, done, notdone, count)
856-
857- if count:
858- for pick_id in pickings:
859- wf_service = netsvc.LocalService("workflow")
860- wf_service.trg_write(uid, 'stock.picking', pick_id, cr)
861+ prodlot_id = None
862+ expired_date = None
863+ if bn_needed:
864+ prodlot_id = r[3] or None
865+ expired_date = r[2] or None
866+ cr.execute("update stock_move set location_id=%s, product_qty=%s, product_uos_qty=%s, prodlot_id=%s, expired_date=%s, state=%s where id=%s", (r[1], r[0], r[0] * move.product_id.uos_coeff, prodlot_id, expired_date, state, move.id))
867+ while res:
868+ r = res.pop(0)
869+ prodlot_id = False
870+ expired_date = False
871+ if bn_needed and r[1]:
872+ prodlot_id = r[3]
873+ expired_date = r[2]
874+ if r[1]:
875+ if r[1] == move.location_dest_id.id:
876+ state = 'done'
877+ else:
878+ state = 'assigned'
879+ else:
880+ state = 'confirmed'
881+
882+ self.copy(cr, uid, move.id, {'line_number': move.line_number, 'product_qty': r[0], 'product_uos_qty': r[0] * move.product_id.uos_coeff, 'location_id': r[1] or move.location_id.id, 'prodlot_id': prodlot_id, 'expired_date': expired_date, 'state': state})
883+
884+ if done:
885+ self.write(cr, uid, done, {'state': 'done'})
886+ if move_to_assign:
887+ self.write(cr, uid, move_to_assign, {'state': 'assigned'})
888+
889+ count = 0
890+ for pick_id in pickings:
891+ count += pickings[pick_id]
892+ wf_service = netsvc.LocalService("workflow")
893+ wf_service.trg_write(uid, 'stock.picking', pick_id, cr)
894 return count
895
896 def setlast_tracking(self, cr, uid, ids, context=None):
897
898=== modified file 'bin/addons/stock_override/stock.py'
899--- bin/addons/stock_override/stock.py 2019-02-11 09:38:43 +0000
900+++ bin/addons/stock_override/stock.py 2019-03-20 14:43:51 +0000
901@@ -721,17 +721,6 @@
902 return res
903
904 @check_cp_rw
905- def action_assign(self, cr, uid, ids, context=None):
906- if isinstance(ids, (int, long)):
907- ids = [ids]
908- res = super(stock_picking, self).action_assign(cr, uid, ids, context=context)
909- for pick in self.read(cr, uid, ids, ['name'], context=context):
910- self.infolog(cr, uid, 'Check availability ran on stock.picking id:%s (%s)' % (
911- pick['id'], pick['name'],
912- ))
913- return res
914-
915- @check_cp_rw
916 def cancel_assign(self, cr, uid, ids, *args, **kwargs):
917 if isinstance(ids, (int, long)):
918 ids = [ids]
919@@ -888,13 +877,6 @@
920 inv_type = 'out_invoice'
921 return inv_type
922
923- def _hook_get_move_ids(self, cr, uid, *args, **kwargs):
924- move_obj = self.pool.get('stock.move')
925- pick = kwargs['pick']
926- move_ids = move_obj.search(cr, uid, [('picking_id', '=', pick.id),
927- ('state', 'in', ('waiting', 'confirmed'))], order='prodlot_id, product_qty desc')
928-
929- return move_ids
930
931 def draft_force_assign(self, cr, uid, ids, context=None):
932 '''
933@@ -1087,25 +1069,6 @@
934 move_obj.action_assign(cr, uid, not_assigned_move)
935 return True
936
937- def _hook_action_assign_batch(self, cr, uid, ids, context=None):
938- '''
939- Please copy this to your module's method also.
940- This hook belongs to the action_assign method from stock>stock.py>stock_picking class
941-
942- - when product is Expiry date mandatory, we "pre-assign" batch numbers regarding the available quantity
943- and location logic in addition to FEFO logic (First expired first out).
944- '''
945- if isinstance(ids, (int, long)):
946- ids = [ids]
947- if context is None:
948- context = {}
949- move_obj = self.pool.get('stock.move')
950- if not context.get('already_checked'):
951- for pick in self.browse(cr, uid, ids, context=context):
952- # perishable for perishable or batch management
953- move_obj.fefo_update(cr, uid, [move.id for move in pick.move_lines if move.product_id.perishable], context) # FEFO
954- context['already_checked'] = True
955- return super(stock_picking, self)._hook_action_assign_batch(cr, uid, ids, context=context)
956
957 # UF-1617: Handle the new state Shipped of IN
958 def action_shipped_wkf(self, cr, uid, ids, context=None):
959@@ -1805,140 +1768,6 @@
960
961 return super(stock_move, self).copy_data(cr, uid, id, default, context=context)
962
963- def fefo_update(self, cr, uid, ids, context=None):
964- """
965- Update batch, Expiry Date, Location according to FEFO logic
966- """
967- if isinstance(ids, (int, long)):
968- ids = [ids]
969- if context is None:
970- context = {}
971-
972- loc_obj = self.pool.get('stock.location')
973- prodlot_obj = self.pool.get('stock.production.lot')
974- compare_date = context.get('rw_date', False)
975- if compare_date:
976- compare_date = datetime.strptime(compare_date[0:10], '%Y-%m-%d')
977- else:
978- today = datetime.today()
979- compare_date = datetime(today.year, today.month, today.day)
980-
981- for move in self.read(cr, uid, ids,
982- ['date',
983- 'date_expected',
984- 'expired_date',
985- 'line_number',
986- 'location_dest_id',
987- 'location_id',
988- 'move_cross_docking_ok',
989- 'move_dest_id',
990- 'name',
991- 'picking_id',
992- 'prodlot_id',
993- 'product_id',
994- 'product_qty',
995- 'product_uom',
996- 'reason_type_id',
997- 'sale_line_id',
998- 'state'], context):
999- vals = {}
1000- move_unlinked = False
1001- # FEFO logic
1002- if move['state'] == 'assigned' and not move['prodlot_id']: # a check_availability has already been done in action_assign, so we take only the 'assigned' lines
1003- needed_qty = move['product_qty']
1004- res = loc_obj.compute_availability(cr, uid,
1005- [move['location_id'][0]], True, move['product_id'][0],
1006- move['product_uom'][0], context=context)
1007- if 'fefo' in res:
1008- # We need to have the value like below because we need to have the id of the m2o (which is not possible if we do self.read(cr, uid, move.id))
1009- picking_id = move['picking_id'] and move['picking_id'][0] or False
1010- values = {'name': move['name'],
1011- 'sale_line_id': move['sale_line_id'] and move['sale_line_id'][0] or False,
1012- 'picking_id': picking_id,
1013- 'product_uom': move['product_uom'][0],
1014- 'product_id': move['product_id'][0],
1015- 'date_expected': move['date_expected'],
1016- 'date': move['date'],
1017- 'state': 'assigned',
1018- 'location_dest_id': move['location_dest_id'][0],
1019- 'reason_type_id': move['reason_type_id'][0],
1020- }
1021- for loc in res['fefo']:
1022- # if source == destination, the state becomes 'done', so we don't do fefo logic in that case
1023- if not move['location_dest_id'][0] == loc['location_id']:
1024- # as long all needed are not fulfilled
1025- if needed_qty:
1026- # we ignore the batch that are outdated
1027- expired_date = prodlot_obj.read(cr, uid, loc['prodlot_id'], ['life_date'], context)['life_date']
1028- if datetime.strptime(expired_date, "%Y-%m-%d") >= compare_date:
1029- existed_moves = []
1030- if not move['move_dest_id']:
1031- # Search if a stock move with the same location_id and same product_id and same prodlot_id exist
1032- existed_moves = self.search(cr, uid,
1033- [('picking_id', '!=', False),
1034- ('picking_id', '=', picking_id),
1035- ('product_id', '=', move['product_id'][0]),
1036- ('product_uom', '=', loc['uom_id']),
1037- ('line_number', '=', move['line_number']),
1038- ('location_id', '=', loc['location_id']),
1039- ('sale_line_id', '=', move['sale_line_id'] and move['sale_line_id'][0] or False),
1040- ('location_dest_id', '=', move['location_dest_id'][0]),
1041- ('prodlot_id', '=', loc['prodlot_id'])],
1042- context=context)
1043- # if the batch already exists and qty is enough, it is available (assigned)
1044- if needed_qty <= loc['qty']:
1045- if existed_moves:
1046- exist_move = self.read(cr, uid, existed_moves[0], ['product_qty'], context)
1047- self.write(cr, uid, [exist_move['id']], {'product_qty': needed_qty + exist_move['product_qty']}, context)
1048- # We update the linked documents
1049- self.update_linked_documents(cr, uid, [move['id']], exist_move['id'], context=context)
1050- self.unlink(cr, uid, [move['id']],
1051- context, force=True)
1052- move_unlinked = True
1053- else:
1054- vals.update({'product_qty': needed_qty,
1055- 'product_uom': loc['uom_id'],
1056- 'location_id': loc['location_id'],
1057- 'prodlot_id': loc['prodlot_id']
1058- })
1059- needed_qty = 0.0
1060- break
1061- elif needed_qty:
1062- # we take all available
1063- selected_qty = loc['qty']
1064- needed_qty -= selected_qty
1065- dict_for_create = {}
1066- dict_for_create = values.copy()
1067- dict_for_create.update({
1068- 'product_uom': loc['uom_id'],
1069- 'product_qty': selected_qty,
1070- 'location_id': loc['location_id'],
1071- 'prodlot_id': loc['prodlot_id'],
1072- 'line_number': move['line_number'],
1073- 'move_cross_docking_ok': move['move_cross_docking_ok']
1074- })
1075- if existed_moves:
1076- exist_move = self.read(cr, uid, existed_moves[0], ['product_qty'], context)
1077- self.write(cr, uid, [exist_move['id']], {'product_qty': selected_qty + exist_move['product_qty']}, context)
1078- else:
1079- self.create(cr, uid, dict_for_create, context)
1080- vals.update({'product_qty': needed_qty})
1081- # if the batch is outdated, we remove it
1082- if not context.get('yml_test', False):
1083- if not move_unlinked and move['expired_date'] and not\
1084- datetime.strptime(move['expired_date'], "%Y-%m-%d") >= compare_date:
1085- # Don't remove the batch if the move is a chained move
1086- if not self.search(cr, uid,
1087- [('move_dest_id', '=', move['id'])],
1088- limit=1, order='NO_ORDER', context=context):
1089- vals.update({'prodlot_id': False})
1090- elif move['state'] == 'confirmed':
1091- # we remove the prodlot_id in case that the move is not available
1092- vals.update({'prodlot_id': False})
1093- if vals:
1094- self.write(cr, uid, move['id'], vals, context)
1095- return True
1096-
1097 def check_product_quantity(self, cr, uid, ids, context=None):
1098 '''
1099 check that all move have a product quantity > 0
1100@@ -1953,30 +1782,6 @@
1101 if no_product:
1102 raise osv.except_osv(_('Error'), _('You cannot confirm a stock move without quantity.'))
1103
1104- def action_confirm(self, cr, uid, ids, context=None, vals=None):
1105- '''
1106- Set the bool already confirmed to True
1107- '''
1108- if vals is None:
1109- vals = {}
1110- ids = isinstance(ids, (int, long)) and [ids] or ids
1111- self.check_product_quantity(cr, uid, ids, context=context)
1112-
1113- vals = {'already_confirmed': True}
1114- res = super(stock_move, self).action_confirm(cr, uid, ids,
1115- context=context, vals=vals)
1116- return res
1117-
1118- def _hook_confirmed_move(self, cr, uid, *args, **kwargs):
1119- '''
1120- Always return True
1121- '''
1122- already_confirmed = kwargs['already_confirmed']
1123- move_id = kwargs['move_id']
1124- if not already_confirmed:
1125- self.action_confirm(cr, uid, [move_id])
1126- return True
1127-
1128 def _hook_move_cancel_state(self, cr, uid, *args, **kwargs):
1129 '''
1130 Change the state of the chained move
1131@@ -1985,35 +1790,6 @@
1132 kwargs['context'].update({'call_unlink': True})
1133 return {'state': 'cancel'}, kwargs.get('context', {})
1134
1135- def _hook_write_state_stock_move(self, cr, uid, done, notdone, count):
1136- if done:
1137- count += len(done)
1138-
1139- done_ids = []
1140- assigned_ids = []
1141- # If source location == dest location THEN stock move is done.
1142- for line in self.read(cr, uid, done, ['location_id', 'location_dest_id']):
1143- if line.get('location_id') and line.get('location_dest_id') and line.get('location_id') == line.get('location_dest_id'):
1144- done_ids.append(line['id'])
1145- else:
1146- assigned_ids.append(line['id'])
1147-
1148- if done_ids:
1149- self.write(cr, uid, done_ids, {'state': 'done'})
1150- if assigned_ids:
1151- self.write(cr, uid, assigned_ids, {'state': 'assigned'})
1152-
1153- if notdone:
1154- self.write(cr, uid, notdone, {'state': 'confirmed'})
1155- self.action_assign(cr, uid, notdone)
1156- return count
1157-
1158- def _hook_check_assign(self, cr, uid, *args, **kwargs):
1159- '''
1160- kwargs['move'] is the current move
1161- '''
1162- move = kwargs['move']
1163- return move.location_id.usage == 'supplier' or (move.location_id.usage == 'customer' and move.location_id.location_category == 'consumption_unit')
1164
1165 def _hook_cancel_assign_batch(self, cr, uid, ids, context=None):
1166 '''
1167@@ -2123,16 +1899,6 @@
1168
1169 return res
1170
1171- def _hook_copy_stock_move(self, cr, uid, res, move, done, notdone):
1172- while res:
1173- r = res.pop(0)
1174- move_id = self.copy(cr, uid, move.id, {'line_number': move.line_number, 'product_qty': r[0], 'product_uos_qty': r[0] * move.product_id.uos_coeff, 'location_id': r[1]})
1175- if r[2]:
1176- done.append(move_id)
1177- else:
1178- notdone.append(move_id)
1179- return done, notdone
1180-
1181 def _do_partial_hook(self, cr, uid, ids, context, *args, **kwargs):
1182 '''
1183 hook to update defaults data
1184@@ -2589,23 +2355,6 @@
1185 return result
1186 # @@@override end
1187
1188- def _hook_proct_reserve(self, cr, uid, product_qty, result, amount, id, ids):
1189- result.append((amount, id, True))
1190- product_qty -= amount
1191- if isinstance(ids, (int, long)):
1192- ids = [ids]
1193- if product_qty <= 0.0:
1194- return result
1195- else:
1196- result = []
1197- result.append((amount, id, True))
1198- if len(ids) >= 1:
1199- result.append((product_qty, ids[0], False))
1200- else:
1201- result.append((product_qty, id, False))
1202- return result
1203- return []
1204-
1205 def on_change_location_type(self, cr, uid, ids, chained_location_type, context=None):
1206 '''
1207 If the location type is changed to 'Nomenclature', set some other fields values

Subscribers

People subscribed via source and target branches