Merge lp:~jfb-tempo-consulting/unifield-server/US-5366 into lp:unifield-server
- US-5366
- Merge into trunk
Proposed by
jftempo
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
UniField Reviewer Team | Pending | ||
Review via email: mp+364812@code.launchpad.net |
Commit message
Description of the change
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 | 985 | done_moves = [] # Moves that are completed | 985 | done_moves = [] # Moves that are completed |
6 | 986 | out_picks = set() | 986 | out_picks = set() |
7 | 987 | processed_out_moves = [] | 987 | processed_out_moves = [] |
8 | 988 | processed_out_moves_by_exp = {} | ||
9 | 988 | track_changes_to_create = [] # list of dict that contains data on track changes to create at the method's end | 989 | track_changes_to_create = [] # list of dict that contains data on track changes to create at the method's end |
10 | 989 | 990 | ||
11 | 990 | picking_move_lines = move_obj.browse(cr, uid, picking_dict['move_lines'], | 991 | picking_move_lines = move_obj.browse(cr, uid, picking_dict['move_lines'], |
12 | @@ -1148,6 +1149,8 @@ | |||
13 | 1148 | # search for sol that match with the updated move: | 1149 | # search for sol that match with the updated move: |
14 | 1149 | move_obj.write(cr, uid, [out_move.id], move_values, context=context) | 1150 | move_obj.write(cr, uid, [out_move.id], move_values, context=context) |
15 | 1150 | processed_out_moves.append(new_out_move_id) | 1151 | processed_out_moves.append(new_out_move_id) |
16 | 1152 | processed_out_moves_by_exp.setdefault(line.prodlot_id and line.prodlot_id.life_date or False, []).append(new_out_move_id) | ||
17 | 1153 | |||
18 | 1151 | elif uom_partial_qty == out_move.product_qty and out_move.id not in processed_out_moves: | 1154 | elif uom_partial_qty == out_move.product_qty and out_move.id not in processed_out_moves: |
19 | 1152 | out_values.update({ | 1155 | out_values.update({ |
20 | 1153 | 'product_qty': remaining_out_qty, | 1156 | 'product_qty': remaining_out_qty, |
21 | @@ -1157,6 +1160,7 @@ | |||
22 | 1157 | remaining_out_qty = 0.00 | 1160 | remaining_out_qty = 0.00 |
23 | 1158 | move_obj.write(cr, uid, [out_move.id], out_values, context=context) | 1161 | move_obj.write(cr, uid, [out_move.id], out_values, context=context) |
24 | 1159 | processed_out_moves.append(out_move.id) | 1162 | processed_out_moves.append(out_move.id) |
25 | 1163 | processed_out_moves_by_exp.setdefault(line.prodlot_id and line.prodlot_id.life_date or False, []).append(out_move.id) | ||
26 | 1160 | 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: | 1164 | 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 | 1161 | # Just update the out move with the value of the out move with UoM of IN | 1165 | # Just update the out move with the value of the out move with UoM of IN |
28 | 1162 | out_qty = out_move.product_qty | 1166 | out_qty = out_move.product_qty |
29 | @@ -1171,6 +1175,7 @@ | |||
30 | 1171 | remaining_out_qty -= out_qty | 1175 | remaining_out_qty -= out_qty |
31 | 1172 | move_obj.write(cr, uid, [out_move.id], out_values, context=context) | 1176 | move_obj.write(cr, uid, [out_move.id], out_values, context=context) |
32 | 1173 | processed_out_moves.append(out_move.id) | 1177 | processed_out_moves.append(out_move.id) |
33 | 1178 | processed_out_moves_by_exp.setdefault(line.prodlot_id and line.prodlot_id.life_date or False, []).append(out_move.id) | ||
34 | 1174 | else: | 1179 | else: |
35 | 1175 | # Just update the data of the initial out move | 1180 | # Just update the data of the initial out move |
36 | 1176 | processed_qty = lst_out_move is out_moves[-1] and uom_partial_qty - minus_qty or out_move.product_qty | 1181 | 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 | 1184 | new_out_move_id = move_obj.copy(cr, uid, out_move.id, out_values, context=context) | 1189 | new_out_move_id = move_obj.copy(cr, uid, out_move.id, out_values, context=context) |
39 | 1185 | context['keepLineNumber'] = False | 1190 | context['keepLineNumber'] = False |
40 | 1186 | processed_out_moves.append(new_out_move_id) | 1191 | processed_out_moves.append(new_out_move_id) |
41 | 1192 | processed_out_moves_by_exp.setdefault(line.prodlot_id and line.prodlot_id.life_date or False, []).append(new_out_move_id) | ||
42 | 1187 | else: | 1193 | else: |
43 | 1188 | move_obj.write(cr, uid, [out_move.id], out_values, context=context) | 1194 | move_obj.write(cr, uid, [out_move.id], out_values, context=context) |
44 | 1189 | processed_out_moves.append(out_move.id) | 1195 | processed_out_moves.append(out_move.id) |
45 | 1196 | processed_out_moves_by_exp.setdefault(line.prodlot_id and line.prodlot_id.life_date or False, []).append(out_move.id) | ||
46 | 1190 | 1197 | ||
47 | 1191 | if line.uom_id.id != out_move.product_uom.id: | 1198 | if line.uom_id.id != out_move.product_uom.id: |
48 | 1192 | uom_processed_qty = uom_obj._compute_qty(cr, uid, out_move.product_uom.id, processed_qty, line.uom_id.id) | 1199 | 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 | 1437 | )) | 1444 | )) |
51 | 1438 | 1445 | ||
52 | 1439 | if not sync_in and wizard.claim_type not in ('scrap', 'quarantine', 'return', 'missing'): | 1446 | if not sync_in and wizard.claim_type not in ('scrap', 'quarantine', 'return', 'missing'): |
54 | 1440 | move_obj.action_assign(cr, uid, processed_out_moves) | 1447 | to_process = [] |
55 | 1448 | for ed in sorted(processed_out_moves_by_exp.keys()): | ||
56 | 1449 | to_process += processed_out_moves_by_exp[ed] | ||
57 | 1450 | move_obj.action_assign(cr, uid, to_process) | ||
58 | 1441 | 1451 | ||
59 | 1442 | # create track changes: | 1452 | # create track changes: |
60 | 1443 | for tc_data in track_changes_to_create: | 1453 | for tc_data in track_changes_to_create: |
61 | 1444 | 1454 | ||
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 | 1402 | #print res | 1402 | #print res |
67 | 1403 | return res | 1403 | return res |
68 | 1404 | 1404 | ||
70 | 1405 | def _product_reserve_lot(self, cr, uid, ids, product_id, uom_id, context=None, lock=False): | 1405 | def _product_reserve_lot(self, cr, uid, ids, product_id, needed_qty, uom_id, context=None, lock=False): |
71 | 1406 | """ | 1406 | """ |
72 | 1407 | refactoring of original reserver method, taking production lot into account | 1407 | refactoring of original reserver method, taking production lot into account |
73 | 1408 | 1408 | ||
74 | 1409 | returning the original list-tuple structure + the total qty in each location | 1409 | returning the original list-tuple structure + the total qty in each location |
75 | 1410 | """ | 1410 | """ |
76 | 1411 | |||
77 | 1412 | # TODO : how to deal with product attrbiutes changes (ie: from BN managed to not BN, from not BN to BN ?) | ||
78 | 1413 | # TODO : update compute_availability used in kit / clail | ||
79 | 1411 | amount = 0.0 | 1414 | amount = 0.0 |
80 | 1412 | if context is None: | 1415 | if context is None: |
81 | 1413 | context = {} | 1416 | context = {} |
82 | @@ -1418,109 +1421,102 @@ | |||
83 | 1418 | location_ids = self.search(cr, uid, [('location_id', 'child_of', ids)], context=context) | 1421 | location_ids = self.search(cr, uid, [('location_id', 'child_of', ids)], context=context) |
84 | 1419 | else: | 1422 | else: |
85 | 1420 | location_ids = ids | 1423 | location_ids = ids |
189 | 1421 | # fefo list of lot | 1424 | |
190 | 1422 | fefo_list = [] | 1425 | # lock the database if needed |
191 | 1423 | # data structure | 1426 | if lock: |
192 | 1424 | data = {'fefo': fefo_list, 'total': 0.0} | 1427 | try: |
193 | 1425 | 1428 | # Must lock with a separate select query because FOR UPDATE can't be used with | |
194 | 1426 | for id in location_ids: | 1429 | # aggregation/group by's (when individual rows aren't identifiable). |
195 | 1427 | # set up default value | 1430 | # We use a SAVEPOINT to be able to rollback this part of the transaction without |
196 | 1428 | data.setdefault(id, {}).setdefault('total', 0.0) | 1431 | # failing the whole transaction in case the LOCK cannot be acquired. |
197 | 1429 | # lock the database if needed | 1432 | cr.execute("SAVEPOINT stock_location_product_reserve_lot") |
198 | 1430 | if lock: | 1433 | cr.execute("""SELECT id FROM stock_move |
199 | 1431 | try: | 1434 | WHERE product_id=%s AND |
200 | 1432 | # Must lock with a separate select query because FOR UPDATE can't be used with | 1435 | ( |
201 | 1433 | # aggregation/group by's (when individual rows aren't identifiable). | 1436 | (location_dest_id in %s AND |
202 | 1434 | # We use a SAVEPOINT to be able to rollback this part of the transaction without | 1437 | location_id<>location_dest_id AND |
203 | 1435 | # failing the whole transaction in case the LOCK cannot be acquired. | 1438 | state='done') |
204 | 1436 | cr.execute("SAVEPOINT stock_location_product_reserve_lot") | 1439 | OR |
205 | 1437 | cr.execute("""SELECT id FROM stock_move | 1440 | (location_id in %s AND |
206 | 1438 | WHERE product_id=%s AND | 1441 | location_dest_id<>location_id AND |
207 | 1439 | ( | 1442 | state in ('done', 'assigned')) |
208 | 1440 | (location_dest_id=%s AND | 1443 | ) |
209 | 1441 | location_id<>%s AND | 1444 | FOR UPDATE of stock_move NOWAIT""", (product_id, tuple(location_ids), tuple(location_ids)), log_exceptions=False) |
210 | 1442 | state='done') | 1445 | except Exception: |
211 | 1443 | OR | 1446 | # Here it's likely that the FOR UPDATE NOWAIT failed to get the LOCK, |
212 | 1444 | (location_id=%s AND | 1447 | # so we ROLLBACK to the SAVEPOINT to restore the transaction to its earlier |
213 | 1445 | location_dest_id<>%s AND | 1448 | # state, we return False as if the products were not available, and log it: |
214 | 1446 | state in ('done', 'assigned')) | 1449 | cr.execute("ROLLBACK TO stock_location_product_reserve_lot") |
215 | 1447 | ) | 1450 | logger = logging.getLogger('stock.location') |
216 | 1448 | FOR UPDATE of stock_move NOWAIT""", (product_id, id, id, id, id), log_exceptions=False) | 1451 | 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 | 1449 | except Exception: | 1452 | logger.debug("Trace of the failed product reservation attempt: ", exc_info=True) |
218 | 1450 | # Here it's likely that the FOR UPDATE NOWAIT failed to get the LOCK, | 1453 | return {} |
219 | 1451 | # so we ROLLBACK to the SAVEPOINT to restore the transaction to its earlier | 1454 | |
220 | 1452 | # state, we return False as if the products were not available, and log it: | 1455 | # SQL request is FEFO by default |
221 | 1453 | cr.execute("ROLLBACK TO stock_location_product_reserve_lot") | 1456 | # TODO merge different UOM directly in SQL statement |
222 | 1454 | logger = logging.getLogger('stock.location') | 1457 | # example in class stock_report_prodlots_virtual(osv.osv): in report_stock_virtual.py |
223 | 1455 | 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) | 1458 | # class report_stock_inventory(osv.osv): in specific_rules.py |
224 | 1456 | logger.debug("Trace of the failed product reservation attempt: ", exc_info=True) | 1459 | |
225 | 1457 | return False | 1460 | factor = pool_uom.read(cr, uid, uom_id, ['factor'])['factor'] |
226 | 1458 | 1461 | cr.execute(""" | |
227 | 1459 | # SQL request is FEFO by default | 1462 | SELECT subs.location, subs.parent_left, subs.prodlot_id, subs.expired_date, sum(subs.product_qty) AS product_qty FROM |
228 | 1460 | # TODO merge different UOM directly in SQL statement | 1463 | (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 | 1461 | # example in class stock_report_prodlots_virtual(osv.osv): in report_stock_virtual.py | 1464 | FROM stock_move m |
230 | 1462 | # class report_stock_inventory(osv.osv): in specific_rules.py | 1465 | LEFT JOIN stock_production_lot lot ON lot.id = m.prodlot_id |
231 | 1463 | cr.execute(""" | 1466 | LEFT JOIN stock_location loc ON loc.id = m.location_dest_id |
232 | 1464 | SELECT subs.product_uom, subs.prodlot_id, subs.expired_date, sum(subs.product_qty) AS product_qty FROM | 1467 | LEFT JOIN product_uom move_uom ON move_uom.id = m.product_uom |
233 | 1465 | (SELECT product_uom, prodlot_id, expired_date, sum(product_qty) AS product_qty | 1468 | WHERE m.location_dest_id in %s AND |
234 | 1466 | FROM stock_move | 1469 | m.location_id<>m.location_dest_id AND |
235 | 1467 | WHERE location_dest_id=%s AND | 1470 | m.product_id=%s AND |
236 | 1468 | location_id<>%s AND | 1471 | m.state='done' AND |
237 | 1469 | product_id=%s AND | 1472 | (expired_date is null or expired_date >= CURRENT_DATE) |
238 | 1470 | state='done' | 1473 | GROUP BY m.location_dest_id, loc.parent_left, m.prodlot_id, lot.life_date |
239 | 1471 | GROUP BY product_uom, prodlot_id, expired_date | 1474 | |
240 | 1472 | 1475 | UNION | |
241 | 1473 | UNION | 1476 | |
242 | 1474 | 1477 | 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 | 1475 | SELECT product_uom, prodlot_id, expired_date, -sum(product_qty) AS product_qty | 1478 | FROM stock_move m |
244 | 1476 | FROM stock_move | 1479 | LEFT JOIN stock_production_lot lot on lot.id = m.prodlot_id |
245 | 1477 | WHERE location_id=%s AND | 1480 | LEFT JOIN stock_location loc on loc.id = m.location_id |
246 | 1478 | location_dest_id<>%s AND | 1481 | LEFT JOIN product_uom move_uom ON move_uom.id = m.product_uom |
247 | 1479 | product_id=%s AND | 1482 | WHERE m.location_id in %s AND |
248 | 1480 | state in ('done', 'assigned') | 1483 | m.location_dest_id<>m.location_id AND |
249 | 1481 | GROUP BY product_uom, prodlot_id, expired_date) as subs | 1484 | m.product_id=%s AND |
250 | 1482 | GROUP BY product_uom, prodlot_id, expired_date | 1485 | m.state in ('done', 'assigned') AND |
251 | 1483 | ORDER BY prodlot_id asc, expired_date asc | 1486 | (expired_date is null or expired_date >= CURRENT_DATE) |
252 | 1484 | """, | 1487 | GROUP BY m.location_id, loc.parent_left, m.prodlot_id, lot.life_date) as subs |
253 | 1485 | (id, id, product_id, id, id, product_id)) | 1488 | GROUP BY location, parent_left, prodlot_id, expired_date |
254 | 1486 | results = cr.dictfetchall() | 1489 | ORDER BY expired_date asc, prodlot_id asc, parent_left |
255 | 1487 | # merge results according to uom if needed | 1490 | """, |
256 | 1488 | for r in results: | 1491 | (tuple(location_ids), product_id, tuple(location_ids), product_id)) |
257 | 1489 | # consolidates the uom | 1492 | |
258 | 1490 | amount = pool_uom._compute_qty(cr, uid, r['product_uom'], r['product_qty'], uom_id) | 1493 | results = [] |
259 | 1491 | # total for all locations | 1494 | for r in cr.dictfetchall(): |
260 | 1492 | total = data.setdefault('total', 0.0) | 1495 | # consolidates the uom |
261 | 1493 | total += amount | 1496 | amount = r['product_qty'] * factor |
262 | 1494 | data.update({'total': total}) | 1497 | # total for all locations |
263 | 1495 | # fill the data structure, total value for location | 1498 | if amount <= 0: |
264 | 1496 | loc_tot = data.setdefault(id, {}).setdefault('total', 0.0) | 1499 | continue |
265 | 1497 | loc_tot += amount | 1500 | if amount >= needed_qty: |
266 | 1498 | data.setdefault(id, {}).update({'total': loc_tot}) | 1501 | if results and results[-1][1:4] == [r['location'], r['expired_date'], r['prodlot_id']]: |
267 | 1499 | # production lot | 1502 | results[-1][0] += needed_qty |
268 | 1500 | lot_tot = data.setdefault(id, {}).setdefault(r['prodlot_id'], {}).setdefault('total', 0.0) | 1503 | else: |
269 | 1501 | lot_tot += amount | 1504 | results.append([needed_qty, r['location'], r['expired_date'], r['prodlot_id']]) |
270 | 1502 | data.setdefault(id, {}).setdefault(r['prodlot_id'], {}).update({'total': lot_tot, 'date': r['expired_date']}) | 1505 | return results |
271 | 1503 | # 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 | 1506 | |
272 | 1504 | # only positive amount are taken into account | 1507 | if results and results[-1][1:4] == [r['location'], r['expired_date'], r['prodlot_id']]: |
273 | 1505 | if r['prodlot_id']: | 1508 | results[-1][0] += amount |
274 | 1506 | # FEFO logic is only meaningful if a production lot is associated | 1509 | else: |
275 | 1507 | if fefo_list and fefo_list[-1]['location_id'] == id and fefo_list[-1]['prodlot_id'] == r['prodlot_id']: | 1510 | results.append([amount, r['location'], r['expired_date'], r['prodlot_id']]) |
276 | 1508 | # simply update the qty | 1511 | |
277 | 1509 | if lot_tot > 0: | 1512 | needed_qty -= amount |
278 | 1510 | fefo_list[-1].update({'qty': lot_tot}) | 1513 | |
279 | 1511 | else: | 1514 | if not results: |
280 | 1512 | fefo_list.pop(-1) | 1515 | return [] |
281 | 1513 | elif lot_tot > 0: | 1516 | if needed_qty: |
282 | 1514 | # append a new dic | 1517 | results.append([needed_qty, False, False, False, False]) |
283 | 1515 | fefo_list.append({'location_id': id, | 1518 | |
284 | 1516 | 'uom_id': uom_id, | 1519 | return results |
182 | 1517 | 'expired_date': r['expired_date'], | ||
183 | 1518 | 'prodlot_id': r['prodlot_id'], | ||
184 | 1519 | 'product_id': product_id, | ||
185 | 1520 | 'qty': lot_tot}) | ||
186 | 1521 | # global FEFO sorting | ||
187 | 1522 | data['fefo'] = sorted(fefo_list, cmp=lambda x, y: cmp(x.get('expired_date'), y.get('expired_date')), reverse=False) | ||
188 | 1523 | return data | ||
285 | 1524 | 1520 | ||
286 | 1525 | stock_location() | 1521 | stock_location() |
287 | 1526 | 1522 | ||
288 | 1527 | 1523 | ||
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 | 628 | 628 | ||
294 | 629 | if data[product_id]['object'].perishable: # perishable for perishable or batch management | 629 | if data[product_id]['object'].perishable: # perishable for perishable or batch management |
295 | 630 | # the product is batch management we use the FEFO list | 630 | # the product is batch management we use the FEFO list |
297 | 631 | for loc in res['fefo']: | 631 | for expired_date in res.get('fefo', []): |
298 | 632 | if not needed_qty: | ||
299 | 633 | break | ||
300 | 632 | # we ignore the batch that are outdated | 634 | # we ignore the batch that are outdated |
301 | 633 | expired_date = prodlot_obj.read(cr, uid, loc['prodlot_id'], ['life_date'], context)['life_date'] | ||
302 | 634 | if datetime.strptime(expired_date, "%Y-%m-%d") < datetime.today(): | 635 | if datetime.strptime(expired_date, "%Y-%m-%d") < datetime.today(): |
303 | 635 | continue | 636 | continue |
335 | 636 | # as long all needed are not fulfilled | 637 | |
336 | 637 | if needed_qty > 0.0: | 638 | for prod_id in res[expired_date]: |
337 | 638 | # we treat the available qty from FEFO list corresponding to needed quantity | 639 | if not needed_qty: |
338 | 639 | if loc['qty'] > needed_qty: | 640 | break |
339 | 640 | # we have everything ! | 641 | for loc_id in res[expired_date][prod_id]: |
340 | 641 | selected_qty = needed_qty | 642 | if not needed_qty: |
341 | 642 | needed_qty = 0.0 | 643 | break |
342 | 643 | else: | 644 | # as long all needed are not fulfilled |
343 | 644 | # we take all available | 645 | if needed_qty > 0.0: |
344 | 645 | selected_qty = loc['qty'] | 646 | # we treat the available qty from FEFO list corresponding to needed quantity |
345 | 646 | needed_qty -= selected_qty | 647 | if res[expired_date][prod_id][loc_id]['total'] > needed_qty: |
346 | 647 | # stock move values | 648 | # we have everything ! |
347 | 648 | values = {'kit_creation_id_stock_move': obj.id, | 649 | selected_qty = needed_qty |
348 | 649 | 'name': data[product_id]['object'].name, | 650 | needed_qty = 0.0 |
349 | 650 | 'picking_id': obj.internal_picking_id_kit_creation.id, | 651 | else: |
350 | 651 | 'product_uom': uom_id, | 652 | # we take all available |
351 | 652 | 'product_id': product_id, | 653 | selected_qty = res[expired_date][prod_id][loc_id]['total'] |
352 | 653 | 'date_expected': context['common']['date'], | 654 | needed_qty -= selected_qty |
353 | 654 | 'date': context['common']['date'], | 655 | # stock move values |
354 | 655 | 'product_qty': selected_qty, | 656 | values = {'kit_creation_id_stock_move': obj.id, |
355 | 656 | 'prodlot_id': loc['prodlot_id'], | 657 | 'name': data[product_id]['object'].name, |
356 | 657 | 'location_id': loc['location_id'], | 658 | 'picking_id': obj.internal_picking_id_kit_creation.id, |
357 | 658 | 'location_dest_id': context['common']['kitting_id'], | 659 | 'product_uom': uom_id, |
358 | 659 | 'state': 'assigned', # available | 660 | 'product_id': product_id, |
359 | 660 | 'reason_type_id': context['common']['reason_type_id'], | 661 | 'date_expected': context['common']['date'], |
360 | 661 | 'to_consume_id_stock_move': data[product_id]['uoms'][uom_id]['to_consume_id'], | 662 | 'date': context['common']['date'], |
361 | 662 | 'original_from_process_stock_move': original_flag, | 663 | 'product_qty': selected_qty, |
362 | 663 | } | 664 | 'prodlot_id': prod_id, |
363 | 664 | move_obj.create(cr, uid, values, context=context) | 665 | 'location_id': loc_id, |
364 | 665 | # we reset original move flag | 666 | 'location_dest_id': context['common']['kitting_id'], |
365 | 666 | original_flag = False | 667 | 'state': 'assigned', # available |
366 | 668 | 'reason_type_id': context['common']['reason_type_id'], | ||
367 | 669 | 'to_consume_id_stock_move': data[product_id]['uoms'][uom_id]['to_consume_id'], | ||
368 | 670 | 'original_from_process_stock_move': original_flag, | ||
369 | 671 | } | ||
370 | 672 | move_obj.create(cr, uid, values, context=context) | ||
371 | 673 | # we reset original move flag | ||
372 | 674 | original_flag = False | ||
373 | 667 | if needed_qty: | 675 | if needed_qty: |
374 | 668 | values = {'kit_creation_id_stock_move': obj.id, | 676 | values = {'kit_creation_id_stock_move': obj.id, |
375 | 669 | 'name': data[product_id]['object'].name, | 677 | 'name': data[product_id]['object'].name, |
376 | @@ -674,7 +682,7 @@ | |||
377 | 674 | 'date': context['common']['date'], | 682 | 'date': context['common']['date'], |
378 | 675 | 'product_qty': needed_qty, | 683 | 'product_qty': needed_qty, |
379 | 676 | 'prodlot_id': False, | 684 | 'prodlot_id': False, |
381 | 677 | 'location_id': loc['location_id'], | 685 | 'location_id': default_location_id, |
382 | 678 | 'location_dest_id': context['common']['kitting_id'], | 686 | 'location_dest_id': context['common']['kitting_id'], |
383 | 679 | 'state': 'confirmed', # not available | 687 | 'state': 'confirmed', # not available |
384 | 680 | 'reason_type_id': context['common']['reason_type_id'], | 688 | 'reason_type_id': context['common']['reason_type_id'], |
385 | @@ -687,18 +695,18 @@ | |||
386 | 687 | 695 | ||
387 | 688 | else: | 696 | else: |
388 | 689 | # the product is not batch management, we use locations in id order | 697 | # the product is not batch management, we use locations in id order |
391 | 690 | for loc in sorted(res.keys()): | 698 | for loc in sorted(res.get(None, {}).get(None, [])): |
392 | 691 | if isinstance(loc, int) and res[loc]['total'] > 0.0: | 699 | if isinstance(loc, int) and res[None][None][loc]['total'] > 0.0: |
393 | 692 | # as long all needed are not fulfilled | 700 | # as long all needed are not fulfilled |
394 | 693 | if needed_qty > 0.0: | 701 | if needed_qty > 0.0: |
395 | 694 | # we treat the available qty from locations corresponding to needed quantity | 702 | # we treat the available qty from locations corresponding to needed quantity |
397 | 695 | if res[loc]['total'] > needed_qty: | 703 | if res[None][None][loc]['total'] > needed_qty: |
398 | 696 | # we have everything ! | 704 | # we have everything ! |
399 | 697 | selected_qty = needed_qty | 705 | selected_qty = needed_qty |
400 | 698 | needed_qty = 0.0 | 706 | needed_qty = 0.0 |
401 | 699 | else: | 707 | else: |
402 | 700 | # we take all available | 708 | # we take all available |
404 | 701 | selected_qty = res[loc]['total'] | 709 | selected_qty = res[None][None][loc]['total'] |
405 | 702 | needed_qty -= selected_qty | 710 | needed_qty -= selected_qty |
406 | 703 | # stock move values | 711 | # stock move values |
407 | 704 | values = {'kit_creation_id_stock_move': obj.id, | 712 | values = {'kit_creation_id_stock_move': obj.id, |
408 | @@ -1392,51 +1400,6 @@ | |||
409 | 1392 | 'target': 'crush', | 1400 | 'target': 'crush', |
410 | 1393 | } | 1401 | } |
411 | 1394 | 1402 | ||
412 | 1395 | def check_assign_lot(self, cr, uid, ids, context=None): | ||
413 | 1396 | """ | ||
414 | 1397 | check the assignation of stock move taking into account lot and FEFO rule | ||
415 | 1398 | """ | ||
416 | 1399 | # treated move ids | ||
417 | 1400 | done = [] | ||
418 | 1401 | count = 0 | ||
419 | 1402 | pickings = {} | ||
420 | 1403 | if context is None: | ||
421 | 1404 | context = {} | ||
422 | 1405 | for move in self.browse(cr, uid, ids, context=context): | ||
423 | 1406 | if self._hook_check_assign(cr, uid, move=move): | ||
424 | 1407 | # if move.product_id.type == 'consu' or move.location_id.usage == 'supplier': | ||
425 | 1408 | if move.state in ('confirmed', 'waiting'): | ||
426 | 1409 | done.append(move.id) | ||
427 | 1410 | pickings[move.picking_id.id] = 1 | ||
428 | 1411 | continue | ||
429 | 1412 | if move.state in ('confirmed', 'waiting'): | ||
430 | 1413 | # Important: we must pass lock=True to _product_reserve() to avoid race conditions and double reservations | ||
431 | 1414 | 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 | 1415 | if res: | ||
433 | 1416 | #_product_available_test depends on the next status for correct functioning | ||
434 | 1417 | #the test does not work correctly if the same product occurs multiple times | ||
435 | 1418 | #in the same order. This is e.g. the case when using the button 'split in two' of | ||
436 | 1419 | #the stock outgoing form | ||
437 | 1420 | self.write(cr, uid, [move.id], {'state':'assigned'}) | ||
438 | 1421 | done.append(move.id) | ||
439 | 1422 | pickings[move.picking_id.id] = 1 | ||
440 | 1423 | r = res.pop(0) | ||
441 | 1424 | 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 | 1425 | |||
443 | 1426 | while res: | ||
444 | 1427 | r = res.pop(0) | ||
445 | 1428 | 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 | 1429 | done.append(move_id) | ||
447 | 1430 | if done: | ||
448 | 1431 | count += len(done) | ||
449 | 1432 | self.write(cr, uid, done, {'state': 'assigned'}) | ||
450 | 1433 | |||
451 | 1434 | if count: | ||
452 | 1435 | for pick_id in pickings: | ||
453 | 1436 | wf_service = netsvc.LocalService("workflow") | ||
454 | 1437 | wf_service.trg_write(uid, 'stock.picking', pick_id, cr) | ||
455 | 1438 | return count | ||
456 | 1439 | |||
457 | 1440 | def unlink(self, cr, uid, ids, context=None, force=False): | 1403 | def unlink(self, cr, uid, ids, context=None, force=False): |
458 | 1441 | ''' | 1404 | ''' |
459 | 1442 | override the function so we prevent deletion of original_from_process_stock_move stock.moves | 1405 | override the function so we prevent deletion of original_from_process_stock_move stock.moves |
460 | 1443 | 1406 | ||
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 | 623 | # we check for the available qty (in:done, out: assigned, done) - consider_child_locations=False | 623 | # we check for the available qty (in:done, out: assigned, done) - consider_child_locations=False |
466 | 624 | res = loc_obj.compute_availability(cr, uid, [location_id], False, product_id, uom_id, context=context) | 624 | res = loc_obj.compute_availability(cr, uid, [location_id], False, product_id, uom_id, context=context) |
467 | 625 | if prodlot_id: | 625 | if prodlot_id: |
470 | 626 | # if a lot is specified, we take this specific qty info - the lot may not be available in this specific location | 626 | qty = 0 |
471 | 627 | qty = res[location_id].get(prodlot_id, False) and res[location_id][prodlot_id]['total'] or 0.0 | 627 | for x in res.get('fefo'): |
472 | 628 | if res.get(x, {}).get(prodlot_id, {}).get(location_id): | ||
473 | 629 | qty = res[x][prodlot_id][location_id].get('total') | ||
474 | 630 | break | ||
475 | 628 | else: | 631 | else: |
476 | 629 | # otherwise we take total according to the location | 632 | # otherwise we take total according to the location |
478 | 630 | qty = res[location_id]['total'] | 633 | qty = res.get(None, {}).get(None, {}).get(location_id, {}).get('total', 0) |
479 | 631 | # update the result | 634 | # update the result |
480 | 632 | result.setdefault('value', {}).update({'qty_substitute_item': qty, | 635 | result.setdefault('value', {}).update({'qty_substitute_item': qty, |
481 | 633 | 'uom_id_substitute_item': uom_id, | 636 | 'uom_id_substitute_item': uom_id, |
482 | 634 | 637 | ||
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 | 638 | todo = new_todo | 638 | todo = new_todo |
488 | 639 | # we rechech availability | 639 | # we rechech availability |
489 | 640 | self.action_assign(cr, uid, todo, context) | 640 | self.action_assign(cr, uid, todo, context) |
490 | 641 | #FEFO | ||
491 | 642 | self.fefo_update(cr, uid, todo, context) | ||
492 | 643 | # below we cancel availability to recheck it | ||
493 | 644 | # stock_picking_id = self.read(cr, uid, todo, ['picking_id'], context=context)[0]['picking_id'][0] | ||
494 | 645 | # picking_todo.append(stock_picking_id) | ||
495 | 646 | # # we cancel availability | ||
496 | 647 | # self.pool.get('stock.picking').cancel_assign(cr, uid, [stock_picking_id]) | ||
497 | 648 | # # we recheck availability | ||
498 | 649 | # self.pool.get('stock.picking').action_assign(cr, uid, [stock_picking_id]) | ||
499 | 650 | # if picking_todo: | ||
500 | 651 | # self.pool.get('stock.picking').check_all_move_cross_docking(cr, uid, picking_todo, context=context) | ||
501 | 652 | return ret | 641 | return ret |
502 | 653 | 642 | ||
503 | 654 | @check_cp_rw | 643 | @check_cp_rw |
504 | @@ -691,17 +680,6 @@ | |||
505 | 691 | todo = new_todo | 680 | todo = new_todo |
506 | 692 | # we rechech availability | 681 | # we rechech availability |
507 | 693 | self.action_assign(cr, uid, todo) | 682 | self.action_assign(cr, uid, todo) |
508 | 694 | |||
509 | 695 | #FEFO | ||
510 | 696 | self.fefo_update(cr, uid, todo, context) | ||
511 | 697 | # stock_picking_id = self.read(cr, uid, todo, ['picking_id'], context=context)[0]['picking_id'][0] | ||
512 | 698 | # picking_todo.append(stock_picking_id) | ||
513 | 699 | # we cancel availability | ||
514 | 700 | # self.pool.get('stock.picking').cancel_assign(cr, uid, [stock_picking_id]) | ||
515 | 701 | # we recheck availability | ||
516 | 702 | # self.pool.get('stock.picking').action_assign(cr, uid, [stock_picking_id]) | ||
517 | 703 | # if picking_todo: | ||
518 | 704 | # self.pool.get('stock.picking').check_all_move_cross_docking(cr, uid, picking_todo, context=context) | ||
519 | 705 | return True | 683 | return True |
520 | 706 | 684 | ||
521 | 707 | stock_move() | 685 | stock_move() |
522 | 708 | 686 | ||
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 | 3079 | 3079 | ||
528 | 3080 | return new_packing_id | 3080 | return new_packing_id |
529 | 3081 | 3081 | ||
530 | 3082 | def _hook_action_assign_raise_exception(self, cr, uid, ids, context=None, *args, **kwargs): | ||
531 | 3083 | ''' | ||
532 | 3084 | Please copy this to your module's method also. | ||
533 | 3085 | This hook belongs to the action_assign method from stock>stock.py>stock_picking class | ||
534 | 3086 | |||
535 | 3087 | - allow to choose wether or not an exception should be raised in case of no stock move | ||
536 | 3088 | ''' | ||
537 | 3089 | res = super(stock_picking, self)._hook_action_assign_raise_exception(cr, uid, ids, context=context, *args, **kwargs) | ||
538 | 3090 | return res and False | ||
539 | 3091 | |||
540 | 3092 | def _hook_log_picking_modify_message(self, cr, uid, ids, context=None, *args, **kwargs): | 3082 | def _hook_log_picking_modify_message(self, cr, uid, ids, context=None, *args, **kwargs): |
541 | 3093 | ''' | 3083 | ''' |
542 | 3094 | stock>stock.py>log_picking | 3084 | stock>stock.py>log_picking |
543 | 3095 | 3085 | ||
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 | 2212 | uom_id = uom_id or product_obj.uom_id.id | 2212 | uom_id = uom_id or product_obj.uom_id.id |
549 | 2213 | res = loc_obj.compute_availability(cr, uid, [location_id], False, product_id, uom_id, context=context) | 2213 | res = loc_obj.compute_availability(cr, uid, [location_id], False, product_id, uom_id, context=context) |
550 | 2214 | if prodlot_id: | 2214 | if prodlot_id: |
553 | 2215 | # if a lot is specified, we take this specific qty info - the lot may not be available in this specific location | 2215 | qty = 0 |
554 | 2216 | qty = res[location_id].get(prodlot_id, False) and res[location_id][prodlot_id]['total'] or 0.0 | 2216 | for x in res.get('fefo'): |
555 | 2217 | if res.get(x, {}).get(prodlot_id, {}).get(location_id): | ||
556 | 2218 | qty = res[x][prodlot_id][location_id].get('total') | ||
557 | 2219 | break | ||
558 | 2217 | else: | 2220 | else: |
559 | 2218 | # otherwise we take total according to the location | 2221 | # otherwise we take total according to the location |
561 | 2219 | qty = res[location_id]['total'] | 2222 | qty = res.get(None, {}).get(None, {}).get(location_id, {}).get('total', 0) |
562 | 2220 | # update the result | 2223 | # update the result |
563 | 2221 | result.setdefault('value', {}).update({'qty_claim_product_line': qty, | 2224 | result.setdefault('value', {}).update({'qty_claim_product_line': qty, |
564 | 2222 | 'uom_id_claim_product_line': uom_id, | 2225 | 'uom_id_claim_product_line': uom_id, |
565 | @@ -2355,11 +2358,15 @@ | |||
566 | 2355 | prodlot_id = obj.lot_id_claim_product_line.id | 2358 | prodlot_id = obj.lot_id_claim_product_line.id |
567 | 2356 | # if the product has a production lot and the production lot exist in the specified location, we take corresponding stock | 2359 | # if the product has a production lot and the production lot exist in the specified location, we take corresponding stock |
568 | 2357 | available_qty = 0.0 | 2360 | available_qty = 0.0 |
571 | 2358 | if prodlot_id and prodlot_id in data[location_id]: | 2361 | |
572 | 2359 | available_qty = data[location_id][prodlot_id]['total'] | 2362 | if prodlot_id: |
573 | 2363 | for x in data.get('fefo'): | ||
574 | 2364 | if data.get(x, {}).data(prodlot_id, {}).data(location_id): | ||
575 | 2365 | available_qty = data[x][prodlot_id][location_id].get('total') | ||
576 | 2366 | break | ||
577 | 2360 | else: | 2367 | else: |
580 | 2361 | # otherwise we take the total quantity for the selected location - no lot for this product | 2368 | # otherwise we take total according to the location |
581 | 2362 | available_qty = data[location_id]['total'] | 2369 | available_qty = data.get(None, {}).get(None, {}).get(location_id, {}).get('total', 0) |
582 | 2363 | result[obj.id].update({'hidden_stock_available_claim_product_line': available_qty}) | 2370 | result[obj.id].update({'hidden_stock_available_claim_product_line': available_qty}) |
583 | 2364 | 2371 | ||
584 | 2365 | return result | 2372 | return result |
585 | 2366 | 2373 | ||
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 | 514 | # run check availability on PICK/OUT: | 514 | # run check availability on PICK/OUT: |
591 | 515 | if picking_data['type'] == 'out' and picking_data['subtype'] in ['picking', 'standard']: | 515 | if picking_data['type'] == 'out' and picking_data['subtype'] in ['picking', 'standard']: |
592 | 516 | self.pool.get('stock.move').action_assign(cr, uid, [move_id]) | 516 | self.pool.get('stock.move').action_assign(cr, uid, [move_id]) |
593 | 517 | self.pool.get('stock.move').fefo_update(cr, uid, [move_id], context=context) | ||
594 | 518 | # self.pool.get('stock.picking').action_assign(cr, uid, [pick_to_use], context=context) | 517 | # self.pool.get('stock.picking').action_assign(cr, uid, [pick_to_use], context=context) |
595 | 519 | if picking_data['type'] == 'internal' and sol.type == 'make_to_stock' and sol.order_id.procurement_request: | 518 | if picking_data['type'] == 'internal' and sol.type == 'make_to_stock' and sol.order_id.procurement_request: |
596 | 520 | wf_service.trg_validate(uid, 'stock.picking', pick_to_use, 'button_confirm', cr) | 519 | wf_service.trg_validate(uid, 'stock.picking', pick_to_use, 'button_confirm', cr) |
597 | 521 | 520 | ||
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 | 264 | prodlot_id_str = (prodlot_id and (' AND prodlot_id = %s ' % str(prodlot_id)) or '') | 264 | prodlot_id_str = (prodlot_id and (' AND prodlot_id = %s ' % str(prodlot_id)) or '') |
603 | 265 | date_str = date_str and ' AND %s '% date_str or '' | 265 | date_str = date_str and ' AND %s '% date_str or '' |
604 | 266 | if 'in' in what: | 266 | if 'in' in what: |
605 | 267 | if not states and context.get('in_states'): | ||
606 | 268 | where[3] = tuple(context['in_states']) | ||
607 | 269 | |||
608 | 267 | # all moves from a location out of the set to a location in the set | 270 | # all moves from a location out of the set to a location in the set |
609 | 268 | cr.execute(""" | 271 | cr.execute(""" |
610 | 269 | select sum(product_qty), product_id, product_uom | 272 | select sum(product_qty), product_id, product_uom |
611 | @@ -275,6 +278,8 @@ | |||
612 | 275 | group by product_id,product_uom""" % (prodlot_id_str, date_str),tuple(where)) # not_a_user_entry | 278 | group by product_id,product_uom""" % (prodlot_id_str, date_str),tuple(where)) # not_a_user_entry |
613 | 276 | results = cr.fetchall() | 279 | results = cr.fetchall() |
614 | 277 | if 'out' in what: | 280 | if 'out' in what: |
615 | 281 | if not states and context.get('out_states'): | ||
616 | 282 | where[3] = tuple(context['out_states']) | ||
617 | 278 | # all moves from a location in the set to a location out of the set | 283 | # all moves from a location in the set to a location out of the set |
618 | 279 | cr.execute(""" | 284 | cr.execute(""" |
619 | 280 | select sum(product_qty), product_id, product_uom | 285 | select sum(product_qty), product_id, product_uom |
620 | @@ -336,6 +341,9 @@ | |||
621 | 336 | c.update({ 'states': ('confirmed','waiting','assigned'), 'what': ('in',) }) | 341 | c.update({ 'states': ('confirmed','waiting','assigned'), 'what': ('in',) }) |
622 | 337 | elif f == 'outgoing_qty': | 342 | elif f == 'outgoing_qty': |
623 | 338 | c.update({ 'states': ('confirmed','waiting','assigned'), 'what': ('out',) }) | 343 | c.update({ 'states': ('confirmed','waiting','assigned'), 'what': ('out',) }) |
624 | 344 | elif f == 'qty_allocable': | ||
625 | 345 | c.updates({'what': ('in', 'out'), 'in_states': ('done',), 'out_states': ('done', 'assigned')}) | ||
626 | 346 | |||
627 | 339 | stock = self.get_product_available(cr, uid, ids, context=c) | 347 | stock = self.get_product_available(cr, uid, ids, context=c) |
628 | 340 | if any(stock.values()): | 348 | if any(stock.values()): |
629 | 341 | for id in ids: | 349 | for id in ids: |
630 | @@ -343,6 +351,7 @@ | |||
631 | 343 | return res | 351 | return res |
632 | 344 | 352 | ||
633 | 345 | _columns = { | 353 | _columns = { |
634 | 354 | '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 | 346 | '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'), | 355 | '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 | 347 | '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'), | 356 | '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 | 348 | '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'), | 357 | '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 | 349 | 358 | ||
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 | 372 | reach the requested product_qty (``qty`` is expressed in the default uom of the product), of False if enough | 372 | reach the requested product_qty (``qty`` is expressed in the default uom of the product), of False if enough |
644 | 373 | products could not be found, or the lock could not be obtained (and ``lock`` was True). | 373 | products could not be found, or the lock could not be obtained (and ``lock`` was True). |
645 | 374 | """ | 374 | """ |
646 | 375 | result = [] | ||
647 | 376 | amount = 0.0 | 375 | amount = 0.0 |
648 | 377 | if context is None: | 376 | if context is None: |
649 | 378 | context = {} | 377 | context = {} |
650 | @@ -383,6 +382,7 @@ | |||
651 | 383 | temp.remove(location_dest_id) | 382 | temp.remove(location_dest_id) |
652 | 384 | temp.append(location_dest_id) | 383 | temp.append(location_dest_id) |
653 | 385 | 384 | ||
654 | 385 | result_qty = [] | ||
655 | 386 | for id in temp: | 386 | for id in temp: |
656 | 387 | if lock: | 387 | if lock: |
657 | 388 | try: | 388 | try: |
658 | @@ -435,25 +435,31 @@ | |||
659 | 435 | """, | 435 | """, |
660 | 436 | (id, id, product_id)) | 436 | (id, id, product_id)) |
661 | 437 | results += cr.dictfetchall() | 437 | results += cr.dictfetchall() |
664 | 438 | total = 0.0 | 438 | total_loc = 0.0 |
663 | 439 | results2 = 0.0 | ||
665 | 440 | 439 | ||
666 | 441 | for r in results: | 440 | for r in results: |
667 | 442 | amount = pool_uom._compute_qty(cr, uid, r['product_uom'], r['product_qty'], context.get('uom', False)) | 441 | amount = pool_uom._compute_qty(cr, uid, r['product_uom'], r['product_qty'], context.get('uom', False)) |
670 | 443 | results2 += amount | 442 | total_loc += amount |
669 | 444 | total += amount | ||
671 | 445 | 443 | ||
673 | 446 | if total <= 0.0: | 444 | if total_loc <= 0.0: |
674 | 447 | continue | 445 | continue |
675 | 448 | 446 | ||
684 | 449 | amount = results2 | 447 | if product_qty <= total_loc: |
685 | 450 | if amount > 0: | 448 | result_qty.append((product_qty, id)) |
686 | 451 | if amount > min(total, product_qty): | 449 | return result_qty |
687 | 452 | amount = min(product_qty, total) | 450 | |
688 | 453 | 451 | result_qty.append((total_loc, id)) | |
689 | 454 | return self._hook_proct_reserve(cr,uid,product_qty,result,amount, id, ids) | 452 | product_qty -= total_loc |
690 | 455 | 453 | ||
691 | 456 | return False | 454 | if not result_qty: |
692 | 455 | # zero available stock | ||
693 | 456 | return [] | ||
694 | 457 | |||
695 | 458 | if product_qty: | ||
696 | 459 | # remaining not available qty | ||
697 | 460 | result_qty.append((product_qty, False)) | ||
698 | 461 | |||
699 | 462 | return result_qty | ||
700 | 457 | 463 | ||
701 | 458 | 464 | ||
702 | 459 | 465 | ||
703 | @@ -802,29 +808,6 @@ | |||
704 | 802 | # TODO: Check locations to see if in the same location ? | 808 | # TODO: Check locations to see if in the same location ? |
705 | 803 | return True | 809 | return True |
706 | 804 | 810 | ||
707 | 805 | def _hook_action_assign_raise_exception(self, cr, uid, ids, context=None, *args, **kwargs): | ||
708 | 806 | ''' | ||
709 | 807 | Please copy this to your module's method also. | ||
710 | 808 | This hook belongs to the action_assign method from stock>stock.py>stock_picking class | ||
711 | 809 | |||
712 | 810 | - allow to choose wether or not an exception should be raised in case of no stock move | ||
713 | 811 | ''' | ||
714 | 812 | return True | ||
715 | 813 | |||
716 | 814 | def _hook_get_move_ids(self, cr, uid, *args, **kwargs): | ||
717 | 815 | pick = kwargs['pick'] | ||
718 | 816 | return [x.id for x in pick.move_lines if x.state in ('waiting', 'confirmed')] | ||
719 | 817 | |||
720 | 818 | def _hook_action_assign_batch(self, cr, uid, ids, context=None): | ||
721 | 819 | ''' | ||
722 | 820 | Please copy this to your module's method also. | ||
723 | 821 | This hook belongs to the action_assign method from stock>stock.py>stock_picking class | ||
724 | 822 | |||
725 | 823 | - when product is Expiry date mandatory, a "pre-assignment" of batch numbers regarding the available quantity | ||
726 | 824 | and location logic in addition to FEFO logic (First expired first out). | ||
727 | 825 | ''' | ||
728 | 826 | return True | ||
729 | 827 | |||
730 | 828 | def action_assign(self, cr, uid, ids, context=None, *args): | 811 | def action_assign(self, cr, uid, ids, context=None, *args): |
731 | 829 | """ Changes state of picking to available if all moves are confirmed. | 812 | """ Changes state of picking to available if all moves are confirmed. |
732 | 830 | @return: True | 813 | @return: True |
733 | @@ -834,13 +817,13 @@ | |||
734 | 834 | if context is None: | 817 | if context is None: |
735 | 835 | context = {} | 818 | context = {} |
736 | 836 | move_obj = self.pool.get('stock.move') | 819 | move_obj = self.pool.get('stock.move') |
742 | 837 | for pick in self.browse(cr, uid, ids): | 820 | for pick in self.read(cr, uid, ids, ['name']): |
743 | 838 | move_ids = self._hook_get_move_ids(cr, uid, pick=pick) | 821 | move_ids = move_obj.search(cr, uid, [('picking_id', '=', pick['id']), |
744 | 839 | if not move_ids: | 822 | ('state', 'in', ('waiting', 'confirmed'))], order='prodlot_id, product_qty desc') |
740 | 840 | if self._hook_action_assign_raise_exception(cr, uid, ids, context=context,): | ||
741 | 841 | raise osv.except_osv(_('Warning !'),_('Not enough stock, unable to reserve the products.')) | ||
745 | 842 | move_obj.action_assign(cr, uid, move_ids) | 823 | move_obj.action_assign(cr, uid, move_ids) |
747 | 843 | self._hook_action_assign_batch(cr, uid, ids, context=context) | 824 | self.infolog(cr, uid, 'Check availability ran on stock.picking id:%s (%s)' % ( |
748 | 825 | pick['id'], pick['name'], | ||
749 | 826 | )) | ||
750 | 844 | return True | 827 | return True |
751 | 845 | 828 | ||
752 | 846 | def force_assign(self, cr, uid, ids, *args): | 829 | def force_assign(self, cr, uid, ids, *args): |
753 | @@ -2431,26 +2414,25 @@ | |||
754 | 2431 | if isinstance(ids, (int, long)): | 2414 | if isinstance(ids, (int, long)): |
755 | 2432 | ids = [ids] | 2415 | ids = [ids] |
756 | 2433 | 2416 | ||
758 | 2434 | vals.update({'state': 'confirmed'}) | 2417 | # check qty > 0 or raise |
759 | 2418 | self.check_product_quantity(cr, uid, ids, context=context) | ||
760 | 2419 | |||
761 | 2420 | vals.update({'state': 'confirmed', 'already_confirmed': True}) | ||
762 | 2435 | self.write(cr, uid, ids, vals) | 2421 | self.write(cr, uid, ids, vals) |
763 | 2436 | self.prepare_action_confirm(cr, uid, ids, context=context) | 2422 | self.prepare_action_confirm(cr, uid, ids, context=context) |
764 | 2437 | return [] | 2423 | return [] |
765 | 2438 | 2424 | ||
766 | 2439 | def _hook_confirmed_move(self, cr, uid, *args, **kwargs): | ||
767 | 2440 | ''' | ||
768 | 2441 | Always return True | ||
769 | 2442 | ''' | ||
770 | 2443 | return True | ||
771 | 2444 | 2425 | ||
772 | 2445 | def action_assign(self, cr, uid, ids, *args): | 2426 | def action_assign(self, cr, uid, ids, *args): |
773 | 2446 | """ Changes state to confirmed or waiting. | 2427 | """ Changes state to confirmed or waiting. |
774 | 2447 | @return: List of values | 2428 | @return: List of values |
775 | 2448 | """ | 2429 | """ |
776 | 2449 | todo = [] | 2430 | todo = [] |
781 | 2450 | for move in self.read(cr, uid, ids, ['state', 'already_confirmed']): | 2431 | for move in self.browse(cr, uid, ids, fields_to_fetch=['state', 'already_confirmed']): |
782 | 2451 | self._hook_confirmed_move(cr, uid, already_confirmed=move['already_confirmed'], move_id=move['id']) | 2432 | if not move.already_confirmed: |
783 | 2452 | if move['state'] in ('confirmed', 'waiting'): | 2433 | self.action_confirm(cr, uid, [move.id]) |
784 | 2453 | todo.append(move['id']) | 2434 | if move.state in ('confirmed', 'waiting'): |
785 | 2435 | todo.append(move.id) | ||
786 | 2454 | res = self.check_assign(cr, uid, todo) | 2436 | res = self.check_assign(cr, uid, todo) |
787 | 2455 | return res | 2437 | return res |
788 | 2456 | 2438 | ||
789 | @@ -2478,13 +2460,6 @@ | |||
790 | 2478 | self.write(cr, uid, ids, {'state': 'confirmed'}) | 2460 | self.write(cr, uid, ids, {'state': 'confirmed'}) |
791 | 2479 | return True | 2461 | return True |
792 | 2480 | 2462 | ||
793 | 2481 | def _hook_check_assign(self, cr, uid, *args, **kwargs): | ||
794 | 2482 | ''' | ||
795 | 2483 | kwargs['move'] is the current move | ||
796 | 2484 | ''' | ||
797 | 2485 | move = kwargs['move'] | ||
798 | 2486 | return move.product_id.type == 'consu' or move.location_id.usage == 'supplier' | ||
799 | 2487 | |||
800 | 2488 | # | 2463 | # |
801 | 2489 | # Duplicate stock.move | 2464 | # Duplicate stock.move |
802 | 2490 | # | 2465 | # |
803 | @@ -2493,40 +2468,70 @@ | |||
804 | 2493 | @return: No. of moves done | 2468 | @return: No. of moves done |
805 | 2494 | """ | 2469 | """ |
806 | 2495 | done = [] | 2470 | done = [] |
808 | 2496 | notdone = [] | 2471 | move_to_assign = [] |
809 | 2497 | count = 0 | 2472 | count = 0 |
810 | 2498 | pickings = {} | 2473 | pickings = {} |
811 | 2499 | if context is None: | 2474 | if context is None: |
812 | 2500 | context = {} | 2475 | context = {} |
814 | 2501 | move_to_assign = set() | 2476 | |
815 | 2502 | for move in self.browse(cr, uid, ids, context=context): | 2477 | for move in self.browse(cr, uid, ids, context=context): |
818 | 2503 | if self._hook_check_assign(cr, uid, move=move): | 2478 | if move.location_id.usage == 'supplier' or (move.location_id.usage == 'customer' and move.location_id.location_category == 'consumption_unit'): |
817 | 2504 | # if move.product_id.type == 'consu' or move.location_id.usage == 'supplier': | ||
819 | 2505 | if move.state in ('confirmed', 'waiting'): | 2479 | if move.state in ('confirmed', 'waiting'): |
822 | 2506 | done.append(move.id) | 2480 | if move.location_id.id == move.location_dest_id.id: |
823 | 2507 | pickings[move.picking_id.id] = 1 | 2481 | done.append(move.id) |
824 | 2482 | else: | ||
825 | 2483 | move_to_assign.append(move.id) | ||
826 | 2484 | pickings.setdefault(move.picking_id.id, 0) | ||
827 | 2485 | pickings[move.picking_id.id] += 1 | ||
828 | 2508 | continue | 2486 | continue |
829 | 2509 | if move.state in ('confirmed', 'waiting'): | 2487 | if move.state in ('confirmed', 'waiting'): |
830 | 2488 | bn_needed = move.product_id.perishable | ||
831 | 2510 | # Important: we must pass lock=True to _product_reserve() to avoid race conditions and double reservations | 2489 | # Important: we must pass lock=True to _product_reserve() to avoid race conditions and double reservations |
833 | 2511 | 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) | 2490 | 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 | 2512 | if res: | 2491 | if res: |
842 | 2513 | # _product_available_test depends on the next status for correct functioning | 2492 | if move.location_id.id == move.location_dest_id.id: |
843 | 2514 | # the test does not work correctly if the same product occurs multiple times | 2493 | state = 'done' |
844 | 2515 | # in the same order. This is e.g. the case when using the button 'split in two' of | 2494 | done.append(move.id) |
845 | 2516 | # the stock outgoing form | 2495 | else: |
846 | 2517 | move_to_assign.add(move.id) | 2496 | state = 'assigned' |
847 | 2518 | done.append(move.id) | 2497 | move_to_assign.append(move.id) |
848 | 2519 | pickings[move.picking_id.id] = 1 | 2498 | |
849 | 2499 | pickings.setdefault(move.picking_id.id, 0) | ||
850 | 2500 | pickings[move.picking_id.id] += 1 | ||
851 | 2520 | r = res.pop(0) | 2501 | r = res.pop(0) |
861 | 2521 | 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)) | 2502 | prodlot_id = None |
862 | 2522 | 2503 | expired_date = None | |
863 | 2523 | done, notdone = self._hook_copy_stock_move(cr, uid, res, move, done, notdone) | 2504 | if bn_needed: |
864 | 2524 | count = self._hook_write_state_stock_move(cr, uid, done, notdone, count) | 2505 | prodlot_id = r[3] or None |
865 | 2525 | 2506 | expired_date = r[2] or None | |
866 | 2526 | if count: | 2507 | 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 | 2527 | for pick_id in pickings: | 2508 | while res: |
868 | 2528 | wf_service = netsvc.LocalService("workflow") | 2509 | r = res.pop(0) |
869 | 2529 | wf_service.trg_write(uid, 'stock.picking', pick_id, cr) | 2510 | prodlot_id = False |
870 | 2511 | expired_date = False | ||
871 | 2512 | if bn_needed and r[1]: | ||
872 | 2513 | prodlot_id = r[3] | ||
873 | 2514 | expired_date = r[2] | ||
874 | 2515 | if r[1]: | ||
875 | 2516 | if r[1] == move.location_dest_id.id: | ||
876 | 2517 | state = 'done' | ||
877 | 2518 | else: | ||
878 | 2519 | state = 'assigned' | ||
879 | 2520 | else: | ||
880 | 2521 | state = 'confirmed' | ||
881 | 2522 | |||
882 | 2523 | 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 | 2524 | |||
884 | 2525 | if done: | ||
885 | 2526 | self.write(cr, uid, done, {'state': 'done'}) | ||
886 | 2527 | if move_to_assign: | ||
887 | 2528 | self.write(cr, uid, move_to_assign, {'state': 'assigned'}) | ||
888 | 2529 | |||
889 | 2530 | count = 0 | ||
890 | 2531 | for pick_id in pickings: | ||
891 | 2532 | count += pickings[pick_id] | ||
892 | 2533 | wf_service = netsvc.LocalService("workflow") | ||
893 | 2534 | wf_service.trg_write(uid, 'stock.picking', pick_id, cr) | ||
894 | 2530 | return count | 2535 | return count |
895 | 2531 | 2536 | ||
896 | 2532 | def setlast_tracking(self, cr, uid, ids, context=None): | 2537 | def setlast_tracking(self, cr, uid, ids, context=None): |
897 | 2533 | 2538 | ||
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 | 721 | return res | 721 | return res |
903 | 722 | 722 | ||
904 | 723 | @check_cp_rw | 723 | @check_cp_rw |
905 | 724 | def action_assign(self, cr, uid, ids, context=None): | ||
906 | 725 | if isinstance(ids, (int, long)): | ||
907 | 726 | ids = [ids] | ||
908 | 727 | res = super(stock_picking, self).action_assign(cr, uid, ids, context=context) | ||
909 | 728 | for pick in self.read(cr, uid, ids, ['name'], context=context): | ||
910 | 729 | self.infolog(cr, uid, 'Check availability ran on stock.picking id:%s (%s)' % ( | ||
911 | 730 | pick['id'], pick['name'], | ||
912 | 731 | )) | ||
913 | 732 | return res | ||
914 | 733 | |||
915 | 734 | @check_cp_rw | ||
916 | 735 | def cancel_assign(self, cr, uid, ids, *args, **kwargs): | 724 | def cancel_assign(self, cr, uid, ids, *args, **kwargs): |
917 | 736 | if isinstance(ids, (int, long)): | 725 | if isinstance(ids, (int, long)): |
918 | 737 | ids = [ids] | 726 | ids = [ids] |
919 | @@ -888,13 +877,6 @@ | |||
920 | 888 | inv_type = 'out_invoice' | 877 | inv_type = 'out_invoice' |
921 | 889 | return inv_type | 878 | return inv_type |
922 | 890 | 879 | ||
923 | 891 | def _hook_get_move_ids(self, cr, uid, *args, **kwargs): | ||
924 | 892 | move_obj = self.pool.get('stock.move') | ||
925 | 893 | pick = kwargs['pick'] | ||
926 | 894 | move_ids = move_obj.search(cr, uid, [('picking_id', '=', pick.id), | ||
927 | 895 | ('state', 'in', ('waiting', 'confirmed'))], order='prodlot_id, product_qty desc') | ||
928 | 896 | |||
929 | 897 | return move_ids | ||
930 | 898 | 880 | ||
931 | 899 | def draft_force_assign(self, cr, uid, ids, context=None): | 881 | def draft_force_assign(self, cr, uid, ids, context=None): |
932 | 900 | ''' | 882 | ''' |
933 | @@ -1087,25 +1069,6 @@ | |||
934 | 1087 | move_obj.action_assign(cr, uid, not_assigned_move) | 1069 | move_obj.action_assign(cr, uid, not_assigned_move) |
935 | 1088 | return True | 1070 | return True |
936 | 1089 | 1071 | ||
937 | 1090 | def _hook_action_assign_batch(self, cr, uid, ids, context=None): | ||
938 | 1091 | ''' | ||
939 | 1092 | Please copy this to your module's method also. | ||
940 | 1093 | This hook belongs to the action_assign method from stock>stock.py>stock_picking class | ||
941 | 1094 | |||
942 | 1095 | - when product is Expiry date mandatory, we "pre-assign" batch numbers regarding the available quantity | ||
943 | 1096 | and location logic in addition to FEFO logic (First expired first out). | ||
944 | 1097 | ''' | ||
945 | 1098 | if isinstance(ids, (int, long)): | ||
946 | 1099 | ids = [ids] | ||
947 | 1100 | if context is None: | ||
948 | 1101 | context = {} | ||
949 | 1102 | move_obj = self.pool.get('stock.move') | ||
950 | 1103 | if not context.get('already_checked'): | ||
951 | 1104 | for pick in self.browse(cr, uid, ids, context=context): | ||
952 | 1105 | # perishable for perishable or batch management | ||
953 | 1106 | move_obj.fefo_update(cr, uid, [move.id for move in pick.move_lines if move.product_id.perishable], context) # FEFO | ||
954 | 1107 | context['already_checked'] = True | ||
955 | 1108 | return super(stock_picking, self)._hook_action_assign_batch(cr, uid, ids, context=context) | ||
956 | 1109 | 1072 | ||
957 | 1110 | # UF-1617: Handle the new state Shipped of IN | 1073 | # UF-1617: Handle the new state Shipped of IN |
958 | 1111 | def action_shipped_wkf(self, cr, uid, ids, context=None): | 1074 | def action_shipped_wkf(self, cr, uid, ids, context=None): |
959 | @@ -1805,140 +1768,6 @@ | |||
960 | 1805 | 1768 | ||
961 | 1806 | return super(stock_move, self).copy_data(cr, uid, id, default, context=context) | 1769 | return super(stock_move, self).copy_data(cr, uid, id, default, context=context) |
962 | 1807 | 1770 | ||
963 | 1808 | def fefo_update(self, cr, uid, ids, context=None): | ||
964 | 1809 | """ | ||
965 | 1810 | Update batch, Expiry Date, Location according to FEFO logic | ||
966 | 1811 | """ | ||
967 | 1812 | if isinstance(ids, (int, long)): | ||
968 | 1813 | ids = [ids] | ||
969 | 1814 | if context is None: | ||
970 | 1815 | context = {} | ||
971 | 1816 | |||
972 | 1817 | loc_obj = self.pool.get('stock.location') | ||
973 | 1818 | prodlot_obj = self.pool.get('stock.production.lot') | ||
974 | 1819 | compare_date = context.get('rw_date', False) | ||
975 | 1820 | if compare_date: | ||
976 | 1821 | compare_date = datetime.strptime(compare_date[0:10], '%Y-%m-%d') | ||
977 | 1822 | else: | ||
978 | 1823 | today = datetime.today() | ||
979 | 1824 | compare_date = datetime(today.year, today.month, today.day) | ||
980 | 1825 | |||
981 | 1826 | for move in self.read(cr, uid, ids, | ||
982 | 1827 | ['date', | ||
983 | 1828 | 'date_expected', | ||
984 | 1829 | 'expired_date', | ||
985 | 1830 | 'line_number', | ||
986 | 1831 | 'location_dest_id', | ||
987 | 1832 | 'location_id', | ||
988 | 1833 | 'move_cross_docking_ok', | ||
989 | 1834 | 'move_dest_id', | ||
990 | 1835 | 'name', | ||
991 | 1836 | 'picking_id', | ||
992 | 1837 | 'prodlot_id', | ||
993 | 1838 | 'product_id', | ||
994 | 1839 | 'product_qty', | ||
995 | 1840 | 'product_uom', | ||
996 | 1841 | 'reason_type_id', | ||
997 | 1842 | 'sale_line_id', | ||
998 | 1843 | 'state'], context): | ||
999 | 1844 | vals = {} | ||
1000 | 1845 | move_unlinked = False | ||
1001 | 1846 | # FEFO logic | ||
1002 | 1847 | 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 | 1848 | needed_qty = move['product_qty'] | ||
1004 | 1849 | res = loc_obj.compute_availability(cr, uid, | ||
1005 | 1850 | [move['location_id'][0]], True, move['product_id'][0], | ||
1006 | 1851 | move['product_uom'][0], context=context) | ||
1007 | 1852 | if 'fefo' in res: | ||
1008 | 1853 | # 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 | 1854 | picking_id = move['picking_id'] and move['picking_id'][0] or False | ||
1010 | 1855 | values = {'name': move['name'], | ||
1011 | 1856 | 'sale_line_id': move['sale_line_id'] and move['sale_line_id'][0] or False, | ||
1012 | 1857 | 'picking_id': picking_id, | ||
1013 | 1858 | 'product_uom': move['product_uom'][0], | ||
1014 | 1859 | 'product_id': move['product_id'][0], | ||
1015 | 1860 | 'date_expected': move['date_expected'], | ||
1016 | 1861 | 'date': move['date'], | ||
1017 | 1862 | 'state': 'assigned', | ||
1018 | 1863 | 'location_dest_id': move['location_dest_id'][0], | ||
1019 | 1864 | 'reason_type_id': move['reason_type_id'][0], | ||
1020 | 1865 | } | ||
1021 | 1866 | for loc in res['fefo']: | ||
1022 | 1867 | # if source == destination, the state becomes 'done', so we don't do fefo logic in that case | ||
1023 | 1868 | if not move['location_dest_id'][0] == loc['location_id']: | ||
1024 | 1869 | # as long all needed are not fulfilled | ||
1025 | 1870 | if needed_qty: | ||
1026 | 1871 | # we ignore the batch that are outdated | ||
1027 | 1872 | expired_date = prodlot_obj.read(cr, uid, loc['prodlot_id'], ['life_date'], context)['life_date'] | ||
1028 | 1873 | if datetime.strptime(expired_date, "%Y-%m-%d") >= compare_date: | ||
1029 | 1874 | existed_moves = [] | ||
1030 | 1875 | if not move['move_dest_id']: | ||
1031 | 1876 | # Search if a stock move with the same location_id and same product_id and same prodlot_id exist | ||
1032 | 1877 | existed_moves = self.search(cr, uid, | ||
1033 | 1878 | [('picking_id', '!=', False), | ||
1034 | 1879 | ('picking_id', '=', picking_id), | ||
1035 | 1880 | ('product_id', '=', move['product_id'][0]), | ||
1036 | 1881 | ('product_uom', '=', loc['uom_id']), | ||
1037 | 1882 | ('line_number', '=', move['line_number']), | ||
1038 | 1883 | ('location_id', '=', loc['location_id']), | ||
1039 | 1884 | ('sale_line_id', '=', move['sale_line_id'] and move['sale_line_id'][0] or False), | ||
1040 | 1885 | ('location_dest_id', '=', move['location_dest_id'][0]), | ||
1041 | 1886 | ('prodlot_id', '=', loc['prodlot_id'])], | ||
1042 | 1887 | context=context) | ||
1043 | 1888 | # if the batch already exists and qty is enough, it is available (assigned) | ||
1044 | 1889 | if needed_qty <= loc['qty']: | ||
1045 | 1890 | if existed_moves: | ||
1046 | 1891 | exist_move = self.read(cr, uid, existed_moves[0], ['product_qty'], context) | ||
1047 | 1892 | self.write(cr, uid, [exist_move['id']], {'product_qty': needed_qty + exist_move['product_qty']}, context) | ||
1048 | 1893 | # We update the linked documents | ||
1049 | 1894 | self.update_linked_documents(cr, uid, [move['id']], exist_move['id'], context=context) | ||
1050 | 1895 | self.unlink(cr, uid, [move['id']], | ||
1051 | 1896 | context, force=True) | ||
1052 | 1897 | move_unlinked = True | ||
1053 | 1898 | else: | ||
1054 | 1899 | vals.update({'product_qty': needed_qty, | ||
1055 | 1900 | 'product_uom': loc['uom_id'], | ||
1056 | 1901 | 'location_id': loc['location_id'], | ||
1057 | 1902 | 'prodlot_id': loc['prodlot_id'] | ||
1058 | 1903 | }) | ||
1059 | 1904 | needed_qty = 0.0 | ||
1060 | 1905 | break | ||
1061 | 1906 | elif needed_qty: | ||
1062 | 1907 | # we take all available | ||
1063 | 1908 | selected_qty = loc['qty'] | ||
1064 | 1909 | needed_qty -= selected_qty | ||
1065 | 1910 | dict_for_create = {} | ||
1066 | 1911 | dict_for_create = values.copy() | ||
1067 | 1912 | dict_for_create.update({ | ||
1068 | 1913 | 'product_uom': loc['uom_id'], | ||
1069 | 1914 | 'product_qty': selected_qty, | ||
1070 | 1915 | 'location_id': loc['location_id'], | ||
1071 | 1916 | 'prodlot_id': loc['prodlot_id'], | ||
1072 | 1917 | 'line_number': move['line_number'], | ||
1073 | 1918 | 'move_cross_docking_ok': move['move_cross_docking_ok'] | ||
1074 | 1919 | }) | ||
1075 | 1920 | if existed_moves: | ||
1076 | 1921 | exist_move = self.read(cr, uid, existed_moves[0], ['product_qty'], context) | ||
1077 | 1922 | self.write(cr, uid, [exist_move['id']], {'product_qty': selected_qty + exist_move['product_qty']}, context) | ||
1078 | 1923 | else: | ||
1079 | 1924 | self.create(cr, uid, dict_for_create, context) | ||
1080 | 1925 | vals.update({'product_qty': needed_qty}) | ||
1081 | 1926 | # if the batch is outdated, we remove it | ||
1082 | 1927 | if not context.get('yml_test', False): | ||
1083 | 1928 | if not move_unlinked and move['expired_date'] and not\ | ||
1084 | 1929 | datetime.strptime(move['expired_date'], "%Y-%m-%d") >= compare_date: | ||
1085 | 1930 | # Don't remove the batch if the move is a chained move | ||
1086 | 1931 | if not self.search(cr, uid, | ||
1087 | 1932 | [('move_dest_id', '=', move['id'])], | ||
1088 | 1933 | limit=1, order='NO_ORDER', context=context): | ||
1089 | 1934 | vals.update({'prodlot_id': False}) | ||
1090 | 1935 | elif move['state'] == 'confirmed': | ||
1091 | 1936 | # we remove the prodlot_id in case that the move is not available | ||
1092 | 1937 | vals.update({'prodlot_id': False}) | ||
1093 | 1938 | if vals: | ||
1094 | 1939 | self.write(cr, uid, move['id'], vals, context) | ||
1095 | 1940 | return True | ||
1096 | 1941 | |||
1097 | 1942 | def check_product_quantity(self, cr, uid, ids, context=None): | 1771 | def check_product_quantity(self, cr, uid, ids, context=None): |
1098 | 1943 | ''' | 1772 | ''' |
1099 | 1944 | check that all move have a product quantity > 0 | 1773 | check that all move have a product quantity > 0 |
1100 | @@ -1953,30 +1782,6 @@ | |||
1101 | 1953 | if no_product: | 1782 | if no_product: |
1102 | 1954 | raise osv.except_osv(_('Error'), _('You cannot confirm a stock move without quantity.')) | 1783 | raise osv.except_osv(_('Error'), _('You cannot confirm a stock move without quantity.')) |
1103 | 1955 | 1784 | ||
1104 | 1956 | def action_confirm(self, cr, uid, ids, context=None, vals=None): | ||
1105 | 1957 | ''' | ||
1106 | 1958 | Set the bool already confirmed to True | ||
1107 | 1959 | ''' | ||
1108 | 1960 | if vals is None: | ||
1109 | 1961 | vals = {} | ||
1110 | 1962 | ids = isinstance(ids, (int, long)) and [ids] or ids | ||
1111 | 1963 | self.check_product_quantity(cr, uid, ids, context=context) | ||
1112 | 1964 | |||
1113 | 1965 | vals = {'already_confirmed': True} | ||
1114 | 1966 | res = super(stock_move, self).action_confirm(cr, uid, ids, | ||
1115 | 1967 | context=context, vals=vals) | ||
1116 | 1968 | return res | ||
1117 | 1969 | |||
1118 | 1970 | def _hook_confirmed_move(self, cr, uid, *args, **kwargs): | ||
1119 | 1971 | ''' | ||
1120 | 1972 | Always return True | ||
1121 | 1973 | ''' | ||
1122 | 1974 | already_confirmed = kwargs['already_confirmed'] | ||
1123 | 1975 | move_id = kwargs['move_id'] | ||
1124 | 1976 | if not already_confirmed: | ||
1125 | 1977 | self.action_confirm(cr, uid, [move_id]) | ||
1126 | 1978 | return True | ||
1127 | 1979 | |||
1128 | 1980 | def _hook_move_cancel_state(self, cr, uid, *args, **kwargs): | 1785 | def _hook_move_cancel_state(self, cr, uid, *args, **kwargs): |
1129 | 1981 | ''' | 1786 | ''' |
1130 | 1982 | Change the state of the chained move | 1787 | Change the state of the chained move |
1131 | @@ -1985,35 +1790,6 @@ | |||
1132 | 1985 | kwargs['context'].update({'call_unlink': True}) | 1790 | kwargs['context'].update({'call_unlink': True}) |
1133 | 1986 | return {'state': 'cancel'}, kwargs.get('context', {}) | 1791 | return {'state': 'cancel'}, kwargs.get('context', {}) |
1134 | 1987 | 1792 | ||
1135 | 1988 | def _hook_write_state_stock_move(self, cr, uid, done, notdone, count): | ||
1136 | 1989 | if done: | ||
1137 | 1990 | count += len(done) | ||
1138 | 1991 | |||
1139 | 1992 | done_ids = [] | ||
1140 | 1993 | assigned_ids = [] | ||
1141 | 1994 | # If source location == dest location THEN stock move is done. | ||
1142 | 1995 | for line in self.read(cr, uid, done, ['location_id', 'location_dest_id']): | ||
1143 | 1996 | if line.get('location_id') and line.get('location_dest_id') and line.get('location_id') == line.get('location_dest_id'): | ||
1144 | 1997 | done_ids.append(line['id']) | ||
1145 | 1998 | else: | ||
1146 | 1999 | assigned_ids.append(line['id']) | ||
1147 | 2000 | |||
1148 | 2001 | if done_ids: | ||
1149 | 2002 | self.write(cr, uid, done_ids, {'state': 'done'}) | ||
1150 | 2003 | if assigned_ids: | ||
1151 | 2004 | self.write(cr, uid, assigned_ids, {'state': 'assigned'}) | ||
1152 | 2005 | |||
1153 | 2006 | if notdone: | ||
1154 | 2007 | self.write(cr, uid, notdone, {'state': 'confirmed'}) | ||
1155 | 2008 | self.action_assign(cr, uid, notdone) | ||
1156 | 2009 | return count | ||
1157 | 2010 | |||
1158 | 2011 | def _hook_check_assign(self, cr, uid, *args, **kwargs): | ||
1159 | 2012 | ''' | ||
1160 | 2013 | kwargs['move'] is the current move | ||
1161 | 2014 | ''' | ||
1162 | 2015 | move = kwargs['move'] | ||
1163 | 2016 | return move.location_id.usage == 'supplier' or (move.location_id.usage == 'customer' and move.location_id.location_category == 'consumption_unit') | ||
1164 | 2017 | 1793 | ||
1165 | 2018 | def _hook_cancel_assign_batch(self, cr, uid, ids, context=None): | 1794 | def _hook_cancel_assign_batch(self, cr, uid, ids, context=None): |
1166 | 2019 | ''' | 1795 | ''' |
1167 | @@ -2123,16 +1899,6 @@ | |||
1168 | 2123 | 1899 | ||
1169 | 2124 | return res | 1900 | return res |
1170 | 2125 | 1901 | ||
1171 | 2126 | def _hook_copy_stock_move(self, cr, uid, res, move, done, notdone): | ||
1172 | 2127 | while res: | ||
1173 | 2128 | r = res.pop(0) | ||
1174 | 2129 | 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 | 2130 | if r[2]: | ||
1176 | 2131 | done.append(move_id) | ||
1177 | 2132 | else: | ||
1178 | 2133 | notdone.append(move_id) | ||
1179 | 2134 | return done, notdone | ||
1180 | 2135 | |||
1181 | 2136 | def _do_partial_hook(self, cr, uid, ids, context, *args, **kwargs): | 1902 | def _do_partial_hook(self, cr, uid, ids, context, *args, **kwargs): |
1182 | 2137 | ''' | 1903 | ''' |
1183 | 2138 | hook to update defaults data | 1904 | hook to update defaults data |
1184 | @@ -2589,23 +2355,6 @@ | |||
1185 | 2589 | return result | 2355 | return result |
1186 | 2590 | # @@@override end | 2356 | # @@@override end |
1187 | 2591 | 2357 | ||
1188 | 2592 | def _hook_proct_reserve(self, cr, uid, product_qty, result, amount, id, ids): | ||
1189 | 2593 | result.append((amount, id, True)) | ||
1190 | 2594 | product_qty -= amount | ||
1191 | 2595 | if isinstance(ids, (int, long)): | ||
1192 | 2596 | ids = [ids] | ||
1193 | 2597 | if product_qty <= 0.0: | ||
1194 | 2598 | return result | ||
1195 | 2599 | else: | ||
1196 | 2600 | result = [] | ||
1197 | 2601 | result.append((amount, id, True)) | ||
1198 | 2602 | if len(ids) >= 1: | ||
1199 | 2603 | result.append((product_qty, ids[0], False)) | ||
1200 | 2604 | else: | ||
1201 | 2605 | result.append((product_qty, id, False)) | ||
1202 | 2606 | return result | ||
1203 | 2607 | return [] | ||
1204 | 2608 | |||
1205 | 2609 | def on_change_location_type(self, cr, uid, ids, chained_location_type, context=None): | 2358 | def on_change_location_type(self, cr, uid, ids, chained_location_type, context=None): |
1206 | 2610 | ''' | 2359 | ''' |
1207 | 2611 | If the location type is changed to 'Nomenclature', set some other fields values | 2360 | If the location type is changed to 'Nomenclature', set some other fields values |