[IMP] Optimize orderpoint stock get + traversal of procurement subtraction + change edi format test date_planned to datetime

[IMP] Make sure dropship route can be chosen (takes prio)
This commit is contained in:
Josse Colpaert
2014-12-19 11:07:49 +01:00
parent 0bb26c4761
commit 30bd0ead24
5 changed files with 254 additions and 97 deletions
+22 -36
View File
@@ -1210,6 +1210,28 @@ class procurement_order(osv.osv):
return self.make_po(cr, uid, [procurement.id], context=context)[procurement.id]
return super(procurement_order, self)._run(cr, uid, procurement, context=context)
#TODO: Autocommit needed?
def run(self, cr, uid, ids, autocommit=False, context=None):
procs = self.browse(cr, uid, ids, context=context)
to_assign = [x for x in procs if x.state not in ('running', 'done')]
self._assign_multi(cr, uid, to_assign, context=context)
buy_ids = [x.id for x in procs if x.rule_id and x.rule_id.action == 'buy']
if buy_ids:
result_dict = self.make_po(cr, uid, buy_ids, context=context)
runnings = []
exceptions = []
for proc in result_dict.keys():
if result_dict[proc]:
runnings += [proc]
else:
exceptions += [proc]
if runnings:
self.write(cr, uid, runnings, {'state': 'running'}, context=context)
if exceptions:
self.write(cr, uid, exceptions, {'state': 'exception'}, context=context)
set_others = set(ids) - set(buy_ids)
return super(procurement_order, self).run(cr, uid, list(set_others), context=context)
def _check(self, cr, uid, procurement, context=None):
if procurement.purchase_line_id and procurement.purchase_line_id.order_id.shipped: # TOCHECK: does it work for several deliveries?
return True
@@ -1290,42 +1312,6 @@ class procurement_order(osv.osv):
''' returns the main supplier of the procurement's product given as argument'''
return procurement.product_id.seller_id
def _get_po_line_values_from_proc(self, cr, uid, procurement, partner, company, schedule_date, context=None):
if context is None:
context = {}
uom_obj = self.pool.get('product.uom')
pricelist_obj = self.pool.get('product.pricelist')
prod_obj = self.pool.get('product.product')
acc_pos_obj = self.pool.get('account.fiscal.position')
seller_qty = procurement.product_id.seller_qty
pricelist_id = partner.property_product_pricelist_purchase.id
uom_id = procurement.product_id.uom_po_id.id
qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
if seller_qty:
qty = max(qty, seller_qty)
price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, partner.id, {'uom': uom_id})[pricelist_id]
#Passing partner_id to context for purchase order line integrity of Line name
new_context = context.copy()
new_context.update({'lang': partner.lang, 'partner_id': partner.id})
product = prod_obj.browse(cr, uid, procurement.product_id.id, context=new_context)
taxes_ids = procurement.product_id.supplier_taxes_id
taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position, taxes_ids)
name = product.display_name
if product.description_purchase:
name += '\n' + product.description_purchase
return {
'name': name,
'product_qty': qty,
'product_id': procurement.product_id.id,
'product_uom': uom_id,
'price_unit': price or 0.0,
'date_planned': schedule_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'taxes_id': [(6, 0, taxes)],
}
def _get_po_line_values_from_procs(self, cr, uid, procurements, partner, schedule_date, context=None):
res = {}
if context is None:
@@ -11,14 +11,14 @@
product_uom: 1
price_unit: 60.0
name: 'Headset USB'
date_planned: '2011-08-31'
date_planned: '2011-08-31 12:00:00'
order_line:
- product_id: product.product_product_29
product_qty: 12.0
product_uom: 1
price_unit: 89.0
name: 'Pen drive, 8GB'
date_planned: '2011-08-31'
date_planned: '2011-08-31 12:00:00'
-
I confirm the purchase order
@@ -90,7 +90,7 @@
"name": "PC Assemble SC234",
"product_uom": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_uom_unit", "Unit"],
"product_qty": 1.0,
"date_planned": "2011-09-30",
"date_planned": "2011-09-30 12:00:00",
"sequence": 10,
"price_unit": 150.0,
"product_id": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_product_3", "[PCSC234] PC Assemble SC234"],
@@ -105,7 +105,7 @@
"product_uom": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_uom_unit", "Unit"],
"product_qty": 10.0,
"sequence": 11,
"date_planned": "2011-09-15",
"date_planned": "2011-09-15 12:00:00",
"price_unit": 20.0,
"product_id": ["product:724f9v70-dv70-1v70-8v70-701a04e25v70.product_product_5", "[PC-DEM] PC on Demand"],
}],
@@ -192,7 +192,7 @@
"name": "Basic PC",
"product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "PCE"],
"product_qty": 1.0,
"date_planned": "2011-09-30",
"date_planned": "2011-09-30 12:00:00",
"sequence": 10,
"price_unit": 150.0,
"product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_pc1", "[PC1] Basic PC"],
@@ -207,7 +207,7 @@
"product_uom": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_uom_unit", "PCE"],
"product_qty": 10.0,
"sequence": 11,
"date_planned": "2011-09-15",
"date_planned": "2011-09-15 12:00:00",
"price_unit": 20.0,
"product_id": ["product:724f93ec-ddd0-11e0-88ec-701a04e25543.product_product_pc3", "[PC3] Medium PC"],
}],
+193 -40
View File
@@ -95,7 +95,7 @@ class procurement_order(osv.osv):
move_cancel += [m.id for m in procurement.move_ids]
if move_cancel:
self.pool.get('stock.move').action_cancel(cr, uid, move_cancel, context=context)
return super(procurement_order, self).propagate_cancels(cr, uid, ids, context=context)
return True
def cancel(self, cr, uid, ids, context=None):
if context is None:
@@ -121,6 +121,132 @@ class procurement_order(osv.osv):
return {'value': {'location_id': warehouse.lot_stock_id.id}}
return {}
#Doing assignation, ... in multi
def _assign_multi(self, cr, uid, procurements, context=None):
res = {}
todo_procs = []
context = context or {}
for procurement in procurements:
if procurement.rule_id:
res[procurement.id] = True
elif procurement.product_id.type != 'service':
todo_procs += [procurement]
res_dict = self._find_suitable_rule_multi(cr, uid, todo_procs, context=context)
rule_dict = {}
for proc in res_dict.keys():
if res_dict[proc]:
if rule_dict.get(res_dict[proc]):
rule_dict[res_dict[proc]] += [proc]
else:
rule_dict[res_dict[proc]] = [proc]
for rule in rule_dict.keys():
self.write(cr, uid, rule_dict[rule], {'rule_id': rule}, context=context)
def _get_route_group_dict(self, cr, uid, procurements, context=None):
"""
Returns a dictionary with key the routes and values the products associated
"""
ids = [x.id for x in procurements]
cr.execute("""
SELECT proc_id, route_id FROM
((SELECT p.id AS proc_id, route_id
FROM stock_route_product AS link, procurement_order AS p, product_template AS pt, product_product pp
WHERE pp.product_tmpl_id = pt.id AND link.product_id = pt.id AND pp.id = p.product_id
AND p.id in %s)
UNION (SELECT p.id AS proc_id, link.route_id AS route_id
FROM stock_location_route_categ AS link, product_product AS pp, procurement_order AS p,
product_template AS pt, product_category AS pc, product_category AS pc_product
WHERE p.product_id = pp.id AND pp.product_tmpl_id = pt.id AND pc_product.id = pt.categ_id AND
pc.parent_left <= pc_product.parent_left AND pc.parent_right >= pc_product.parent_left
AND link.categ_id = pc.id AND pp.id IN %s)) p ORDER BY proc_id, route_id
""", (tuple(ids), tuple(ids), ))
product_routes = cr.fetchall()
old_proc = False
key = tuple()
key_routes = {}
for proc, route in product_routes:
key += (route,)
if old_proc != proc:
if key:
if key_routes.get(key):
key_routes[key] += [proc]
else:
key_routes[key] = [proc]
old_proc = proc
key = tuple()
return key_routes
def _get_wh_loc_dict(self, cr, uid, procurements, context=None):
wh_dict = {}
for procurement in procurements:
if wh_dict.get(procurement.warehouse_id.id):
if wh_dict[procurement.warehouse_id.id].get(procurement.location_id):
wh_dict[procurement.warehouse_id.id][procurement.location_id] += [procurement]
else:
wh_dict[procurement.warehouse_id.id][procurement.location_id] = [procurement]
else:
wh_dict[procurement.warehouse_id.id] = {}
wh_dict[procurement.warehouse_id.id][procurement.location_id] = [procurement]
return wh_dict
def _find_suitable_rule_multi(self, cr, uid, procurements, domain = [], context=None):
'''we try to first find a rule among the ones defined on the procurement order group and if none is found, we try on the routes defined for the product, and finally we fallback on the default behavior'''
results_dict = {}
pull_obj = self.pool.get('procurement.rule')
warehouse_route_ids = []
for procurement in procurements: #Could be replaced by one query for all route_ids
if procurement.route_ids:
procurement_route_ids = [x.id for x in procurement.route_ids]
loc = procurement.location_id
loc_domain = [('location_id.parent_left', '<=', loc.parent_left),
('location_id.parent_right', '>=', loc.parent_left)]
if procurement.warehouse_id:
domain += ['|', ('warehouse_id', '=', procurement.warehouse_id.id), ('warehouse_id', '=', False)]
res = pull_obj.search(cr, uid, loc_domain + [('route_id', 'in', procurement_route_ids)], order='route_sequence, sequence', context=context)
if res and res[0]:
results_dict[procurement.id] = res[0]
procurements_to_check = [x for x in procurements if x.id not in results_dict.keys()]
#group by warehouse_id:
wh_dict = self._get_wh_loc_dict(cr, uid, procurements_to_check, context=context)
for wh in wh_dict.keys():
warehouse_route_ids = []
domain = []
check_wh = False
for loc in wh_dict[wh].keys():
procurement = wh_dict[wh][loc][0]
loc_domain = [('location_id.parent_left', '<=', loc.parent_left),
('location_id.parent_right', '>=', loc.parent_left)]
if wh and not check_wh:
domain += ['|', ('warehouse_id', '=', procurement.warehouse_id.id), ('warehouse_id', '=', False)]
warehouse_route_ids = [x.id for x in procurement.warehouse_id.route_ids]
check_wh = True
key_routes = self._get_route_group_dict(cr, uid, wh_dict[wh][loc], context=context)
for key in key_routes.keys():
procurements = self.browse(cr, uid, key_routes[key], context=context)
domain = loc_domain + domain
res = pull_obj.search(cr, uid, domain + [('route_id', 'in', list(key))], order='route_sequence, sequence', context=context)
result = False
if res and res[0]:
result = res[0]
elif warehouse_route_ids:
res = pull_obj.search(cr, uid, domain + [('route_id', 'in', warehouse_route_ids)], order='route_sequence, sequence', context=context)
result = res and res[0]
if not result:
res = pull_obj.search(cr, uid, domain + [('route_id', '=', False)], order='sequence', context=context)
result = res and res[0]
for proc in key_routes[key]:
results_dict[proc] = result
return results_dict
def _search_suitable_rule(self, cr, uid, procurement, domain, context=None):
'''we try to first find a rule among the ones defined on the procurement order group and if none is found, we try on the routes defined for the product, and finally we fallback on the default behavior'''
pull_obj = self.pool.get('procurement.rule')
@@ -325,13 +451,7 @@ class procurement_order(osv.osv):
'group_id': orderpoint.group_id.id,
}
def _product_virtual_get(self, cr, uid, order_point):
product_obj = self.pool.get('product.product')
return product_obj._product_available(cr, uid,
[order_point.product_id.id],
context={'location': order_point.location_id.id})[order_point.product_id.id]['virtual_available']
def _procure_orderpoint_confirm(self, cr, uid, use_new_cursor=False, company_id = False, context=None):
def _procure_orderpoint_confirm(self, cr, uid, use_new_cursor=False, company_id=False, context=None):
'''
Create procurement based on Orderpoint
@@ -343,46 +463,79 @@ class procurement_order(osv.osv):
if use_new_cursor:
cr = openerp.registry(cr.dbname).cursor()
orderpoint_obj = self.pool.get('stock.warehouse.orderpoint')
procurement_obj = self.pool.get('procurement.order')
product_obj = self.pool.get('product.product')
dom = company_id and [('company_id', '=', company_id)] or []
orderpoint_ids = orderpoint_obj.search(cr, uid, dom)
orderpoint_ids = orderpoint_obj.search(cr, uid, dom, order="location_id")
prev_ids = []
tot_procs = []
while orderpoint_ids:
ids = orderpoint_ids[:100]
del orderpoint_ids[:100]
for op in orderpoint_obj.browse(cr, uid, ids, context=context):
try:
prods = self._product_virtual_get(cr, uid, op)
if prods is None:
continue
if prods < op.product_min_qty:
qty = max(op.product_min_qty, op.product_max_qty) - prods
ids = orderpoint_ids[:1000]
del orderpoint_ids[:1000]
product_dict = {}
ops_dict = {}
ops = orderpoint_obj.browse(cr, uid, ids, context=context)
reste = op.qty_multiple > 0 and qty % op.qty_multiple or 0.0
if reste > 0:
qty += op.qty_multiple - reste
#Calculate groups that can be executed together
for op in ops:
key = (op.location_id.id,)
if not product_dict.get(key):
product_dict[key] = [op.product_id]
ops_dict[key] = [op]
else:
product_dict[key] += [op.product_id]
ops_dict[key] += [op]
if qty <= 0:
for key in product_dict.keys():
ctx = context.copy()
ctx.update({'location': ops_dict[key][0].location_id.id})
prod_qty = product_obj._product_available(cr, uid, [x.id for x in product_dict[key]],
context=ctx)
subtract_qty = orderpoint_obj.subtract_procurements_from_orderpoints(cr, uid, [x.id for x in ops_dict[key]], context=context)
for op in ops_dict[key]:
try:
prods = prod_qty[op.product_id.id]['virtual_available']
if prods is None:
continue
if prods <= op.product_min_qty:
qty = max(op.product_min_qty, op.product_max_qty) - prods
reste = op.qty_multiple > 0 and qty % op.qty_multiple or 0.0
if reste > 0:
qty += op.qty_multiple - reste
qty -= orderpoint_obj.subtract_procurements(cr, uid, op, context=context)
if qty < 0:
continue
qty -= subtract_qty[op.id]
if qty >= 0:
proc_id = procurement_obj.create(cr, uid,
self._prepare_orderpoint_procurement(cr, uid, op, qty, context=context),
context=context)
tot_procs.append(proc_id)
if use_new_cursor:
cr.commit()
except OperationalError:
if use_new_cursor:
orderpoint_ids.append(op.id)
cr.rollback()
continue
else:
raise
try:
tot_procs.reverse()
self.run(cr, uid, tot_procs, context=context)
tot_procs = []
if use_new_cursor:
cr.commit()
except OperationalError:
if use_new_cursor:
cr.rollback()
continue
else:
raise
if qty > 0:
proc_id = procurement_obj.create(cr, uid,
self._prepare_orderpoint_procurement(cr, uid, op, qty, context=context),
context=context)
self.check(cr, uid, [proc_id])
self.run(cr, uid, [proc_id])
if use_new_cursor:
cr.commit()
except OperationalError:
if use_new_cursor:
orderpoint_ids.append(op.id)
cr.rollback()
continue
else:
raise
if use_new_cursor:
cr.commit()
if prev_ids == ids:
@@ -393,4 +546,4 @@ class procurement_order(osv.osv):
if use_new_cursor:
cr.commit()
cr.close()
return {}
return {}
+30 -13
View File
@@ -4075,22 +4075,39 @@ class stock_warehouse_orderpoint(osv.osv):
_name = "stock.warehouse.orderpoint"
_description = "Minimum Inventory Rule"
def subtract_procurements(self, cr, uid, orderpoint, context=None):
def subtract_procurements_from_orderpoints(self, cr, uid, orderpoint_ids, context=None):
'''This function returns quantity of product that needs to be deducted from the orderpoint computed quantity because there's already a procurement created with aim to fulfill it.
'''
qty = 0
cr.execute("""select op.id, p.id, p.product_uom, p.product_qty, pt.uom_id, sm.product_qty from procurement_order as p left join stock_move as sm ON sm.procurement_id = p.id,
stock_warehouse_orderpoint op, product_product pp, product_template pt
WHERE p.orderpoint_id = op.id AND p.state not in ('done', 'cancel') AND (sm.state IS NULL OR sm.state not in ('draft'))
AND pp.id = p.product_id AND pp.product_tmpl_id = pt.id
AND op.id IN %s
ORDER BY op.id, p.id
""", (tuple(orderpoint_ids),))
results = cr.fetchall()
current_proc = False
current_op = False
uom_obj = self.pool.get("product.uom")
for procurement in orderpoint.procurement_ids:
if procurement.state in ('cancel', 'done'):
continue
procurement_qty = uom_obj._compute_qty_obj(cr, uid, procurement.product_uom, procurement.product_qty, procurement.product_id.uom_id, context=context)
for move in procurement.move_ids:
#need to add the moves in draft as they aren't in the virtual quantity + moves that have not been created yet
if move.state not in ('draft'):
#if move is already confirmed, assigned or done, the virtual stock is already taking this into account so it shouldn't be deducted
procurement_qty -= move.product_qty
qty += procurement_qty
return qty
op_qty = 0
res = dict.fromkeys(orderpoint_ids, 0.0)
for move_result in results:
op = move_result[0]
if current_op != op:
if current_op:
res[current_op] = op_qty
current_op = op
op_qty = 0
proc = move_result[1]
if proc != current_proc:
op_qty += uom_obj._compute_qty(cr, uid, move_result[2], move_result[3], move_result[4], round=False)
current_proc = proc
if move_result[5]: #If a move is associated (is move qty)
op_qty -= move_result[5]
if current_op:
res[current_op] = op_qty
return res
def _check_product_uom(self, cr, uid, ids, context=None):
'''
+3 -2
View File
@@ -78,6 +78,7 @@
-
!python {model: stock.quant}: |
quant_ids = self.search(cr, uid, [('location_id', '=', ref('stock.stock_location_customers')),('qty', '=', 200), ('product_id', '=', ref("drop_shop_product"))])
print quant_ids, [(x.qty, x.history_ids) for x in self.browse(cr, uid, quant_ids)]
assert quant_ids, 'No Quant found'
assert len(quant_ids) == 1
assert len(self.browse(cr, uid, quant_ids)[0].history_ids) == 1
assert len(quant_ids) == 1, 'There should be exactly one quant'
assert len(self.browse(cr, uid, quant_ids)[0].history_ids) == 1, 'The quant should have exactly 1 move in its history'