[REF] refactoring made during code review. Some optimization patches have been rewritten more clearly, others abandonned or kept. WIP

bzr revid: qdp-launchpad@openerp.com-20140319163359-2ea7tjn5ba1ggein
This commit is contained in:
Quentin (OpenERP) 2014-03-19 17:33:59 +01:00
parent 5300ff660c
commit bde6b6d6e5
7 changed files with 117 additions and 158 deletions

View File

@ -807,10 +807,9 @@ class product_product(osv.osv):
result = [] result = []
for product in self.browse(cr, SUPERUSER_ID, ids, context=context): for product in self.browse(cr, SUPERUSER_ID, ids, context=context):
sellers = []
if partner_id: if partner_id:
sellers = filter(lambda x: x.name.id == partner_id, product.seller_ids) sellers = filter(lambda x: x.name.id == partner_id, product.seller_ids)
else:
sellers = False
if sellers: if sellers:
for s in sellers: for s in sellers:
mydict = { mydict = {

View File

@ -35,7 +35,7 @@ class stock_move(osv.osv):
ids = [ids] ids = [ids]
res = super(stock_move, self).write(cr, uid, ids, vals, context=context) res = super(stock_move, self).write(cr, uid, ids, vals, context=context)
from openerp import workflow from openerp import workflow
if 'state' in vals and vals['state'] in ['done', 'cancel']: if vals.get('state') in ['done', 'cancel']:
for move in self.browse(cr, uid, ids, context=context): for move in self.browse(cr, uid, ids, context=context):
if move.purchase_line_id and move.purchase_line_id.order_id: if move.purchase_line_id and move.purchase_line_id.order_id:
order_id = move.purchase_line_id.order_id.id order_id = move.purchase_line_id.order_id.id

View File

@ -4,4 +4,4 @@
I duplicate order. I duplicate order.
- -
!python {model: purchase.order}: | !python {model: purchase.order}: |
self.copy_data(cr, uid, ref('purchase_order_1'), context) self.copy(cr, uid, ref('purchase_order_1'), context)

View File

@ -702,7 +702,11 @@ class sale_order(osv.osv):
proc_ids.append(proc_id) proc_ids.append(proc_id)
#Confirm procurement order such that rules will be applied on it #Confirm procurement order such that rules will be applied on it
#note that the workflow normally ensure proc_ids isn't an empty list #note that the workflow normally ensure proc_ids isn't an empty list
import time;
a = time.time()
procurement_obj.run(cr, uid, proc_ids, context=context) procurement_obj.run(cr, uid, proc_ids, context=context)
b = time.time()
print "total time in procurement run", b-a
#if shipping was in exception and the user choose to recreate the delivery order, write the new status of SO #if shipping was in exception and the user choose to recreate the delivery order, write the new status of SO
if order.state == 'shipping_except': if order.state == 'shipping_except':

View File

@ -221,16 +221,15 @@ class procurement_order(osv.osv):
return super(procurement_order, self)._run(cr, uid, procurement, context=context) return super(procurement_order, self)._run(cr, uid, procurement, context=context)
def run(self, cr, uid, ids, context=None): def run(self, cr, uid, ids, context=None):
move_obj = self.pool.get('stock.move')
res = super(procurement_order, self).run(cr, uid, ids, context=context) res = super(procurement_order, self).run(cr, uid, ids, context=context)
#after all the procurements are run, check if some created a draft stock move that needs to be confirmed #after all the procurements are run, check if some created a draft stock move that needs to be confirmed
#(we do that in batch because it fasten the picking assignation and the picking state computation) #(we do that in batch because it fasts the picking assignation and the picking state computation)
move_to_confirm_ids = [] move_to_confirm_ids = []
for procurement in self.browse(cr, uid, ids, context=context): for procurement in self.browse(cr, uid, ids, context=context):
if procurement.state == "running" and procurement.rule_id and procurement.rule_id.action == "move": if procurement.state == "running" and procurement.rule_id and procurement.rule_id.action == "move":
move_to_confirm_ids += [m.id for m in procurement.move_ids if m.state == 'draft'] move_to_confirm_ids += [m.id for m in procurement.move_ids if m.state == 'draft']
if move_to_confirm_ids: if move_to_confirm_ids:
move_obj.action_confirm(cr, uid, move_to_confirm_ids, context=context) self.pool.get('stock.move').action_confirm(cr, uid, move_to_confirm_ids, context=context)
return res return res
def _check(self, cr, uid, procurement, context=None): def _check(self, cr, uid, procurement, context=None):

View File

@ -61,6 +61,10 @@ class stock_location(osv.osv):
_order = 'parent_left' _order = 'parent_left'
_rec_name = 'complete_name' _rec_name = 'complete_name'
def _location_owner(self, cr, uid, location, context=None):
''' Return the company owning the location if any '''
return location and (location.usage == 'internal') and location.company_id or False
def _complete_name(self, cr, uid, ids, name, args, context=None): def _complete_name(self, cr, uid, ids, name, args, context=None):
""" Forms complete name of location from parent location to child location. """ Forms complete name of location from parent location to child location.
@return: Dictionary of values @return: Dictionary of values
@ -325,7 +329,6 @@ class stock_quant(osv.osv):
elif reserved_availability > 0 and not move.partially_available: elif reserved_availability > 0 and not move.partially_available:
self.pool.get('stock.move').write(cr, uid, [move.id], {'partially_available': True}, context=context) self.pool.get('stock.move').write(cr, uid, [move.id], {'partially_available': True}, context=context)
def quants_move(self, cr, uid, quants, move, location_to, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, context=None): def quants_move(self, cr, uid, quants, move, location_to, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, context=None):
"""Moves all given stock.quant in the given destination location. """Moves all given stock.quant in the given destination location.
:param quants: list of tuple(browse record(stock.quant) or None, quantity to move) :param quants: list of tuple(browse record(stock.quant) or None, quantity to move)
@ -337,38 +340,37 @@ class stock_quant(osv.osv):
:param dest_package_id: ID of the package that must be set on the moved quant :param dest_package_id: ID of the package that must be set on the moved quant
""" """
quants_reconcile = [] quants_reconcile = []
quants_move = [] to_move_quants = []
self._check_location(cr, uid, location_to, context=context) self._check_location(cr, uid, location_to, context=context)
for quant, qty in quants: for quant, qty in quants:
if not quant: if not quant:
#If quant is None, we will create a quant to move (and potentially a negative counterpart too) #If quant is None, we will create a quant to move (and potentially a negative counterpart too)
quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, force_location = location_to, context=context) quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, force_location=location_to, context=context)
else: else:
quants_move += [(quant, qty)] self._quant_split(cr, uid, quant, qty, context=context)
quant.refresh()
to_move_quants.append(quant)
quants_reconcile.append(quant) quants_reconcile.append(quant)
if quants_move: if to_move_quants:
self.move_single_quant_tuples(cr, uid, quants_move, move, location_to, dest_package_id, context=context) self.move_quants_write(cr, uid, to_move_quants, move, location_to, dest_package_id, context=context)
self._quants_reconcile_negative(cr, uid, quants_reconcile, move, context=context) if location_to.usage == 'internal':
if self.search(cr, uid, [('product_id', '=', move.product_id.id), ('qty','<', 0)], limit=1, context=context):
for quant in quants_reconcile:
def move_single_quant_tuples(self, cr, uid, quants, move, location_dest_id, dest_package_id, context=None): quant.refresh()
whole_quants = [] self._quant_reconcile_negative(cr, uid, quant, move, context=context)
for quant, qty in quants:
new_quant = self._quant_split(cr, uid, quant, qty, context=context)
quant.refresh()
whole_quants.append(quant.id)
if whole_quants:
vals = {'location_id': location_dest_id.id,
'history_ids': [(4, move.id)],
'package_id': dest_package_id}
self.write(cr, SUPERUSER_ID, whole_quants, vals, context=context)
def move_quants_write(self, cr, uid, quants, move, location_dest_id, dest_package_id, context=None):
vals = {'location_id': location_dest_id.id,
'history_ids': [(4, move.id)],
'package_id': dest_package_id}
self.write(cr, SUPERUSER_ID, [q.id for q in quants], vals, context=context)
def quants_get_prefered_domain(self, cr, uid, location, product, qty, domain=None, prefered_domain=False, fallback_domain=False, restrict_lot_id=False, restrict_partner_id=False, context=None): def quants_get_prefered_domain(self, cr, uid, location, product, qty, domain=None, prefered_domain=False, fallback_domain=False, restrict_lot_id=False, restrict_partner_id=False, context=None):
''' This function tries to find quants in the given location for the given domain, by trying to first limit ''' This function tries to find quants in the given location for the given domain, by trying to first limit
the choice on the quants that match the prefered_domain as well. But if the qty requested is not reached the choice on the quants that match the prefered_domain as well. But if the qty requested is not reached
it tries to find the remaining quantity by using the fallback_domain. it tries to find the remaining quantity by using the fallback_domain.
''' '''
#don't look for quants in location that are of type production, supplier or inventory.
if location.usage in ['inventory', 'production', 'supplier']: if location.usage in ['inventory', 'production', 'supplier']:
return [(None, qty)] return [(None, qty)]
if prefered_domain and fallback_domain: if prefered_domain and fallback_domain:
@ -475,16 +477,6 @@ class stock_quant(osv.osv):
path.append((4, move.id)) path.append((4, move.id))
self.write(cr, SUPERUSER_ID, solved_quant_ids, {'history_ids': path}, context=context) self.write(cr, SUPERUSER_ID, solved_quant_ids, {'history_ids': path}, context=context)
def _quants_reconcile_negative(self, cr, uid, quants, move, context=None):
if quants[0].location_id.usage != 'internal':
return False
quants_rec = self.search(cr, uid, [('product_id', '=', move.product_id.id), ('qty','<', 0)], limit=1, context=context)
if quants_rec:
for quant in quants:
quant.refresh()
self._quant_reconcile_negative(cr, uid, quant, move, context=context)
def _quant_reconcile_negative(self, cr, uid, quant, move, context=None): def _quant_reconcile_negative(self, cr, uid, quant, move, context=None):
""" """
When new quant arrive in a location, try to reconcile it with When new quant arrive in a location, try to reconcile it with
@ -551,7 +543,6 @@ class stock_quant(osv.osv):
domain += [('company_id', '=', context.get('force_company'))] domain += [('company_id', '=', context.get('force_company'))]
else: else:
domain += [('company_id', '=', self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id)] domain += [('company_id', '=', self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id)]
#don't take into account location that are production, supplier or inventory
res = [] res = []
offset = 0 offset = 0
while quantity > 0: while quantity > 0:
@ -578,10 +569,6 @@ class stock_quant(osv.osv):
order = 'in_date desc, id desc' order = 'in_date desc, id desc'
return self._quants_get_order(cr, uid, location, product, quantity, domain, order, context=context) return self._quants_get_order(cr, uid, location, product, quantity, domain, order, context=context)
def _location_owner(self, cr, uid, quant, location, context=None):
''' Return the company owning the location if any '''
return location and (location.usage == 'internal') and location.company_id or False
def _check_location(self, cr, uid, location, context=None): def _check_location(self, cr, uid, location, context=None):
if location.usage == 'view': if location.usage == 'view':
raise osv.except_osv(_('Error'), _('You cannot move to a location of type view %s.') % (location.name)) raise osv.except_osv(_('Error'), _('You cannot move to a location of type view %s.') % (location.name))
@ -678,21 +665,19 @@ class stock_picking(osv.osv):
''' '''
res = {} res = {}
for pick in self.browse(cr, uid, ids, context=context): for pick in self.browse(cr, uid, ids, context=context):
cr.execute("select state, partially_available from stock_move where picking_id = %s", (pick.id,)) if (not pick.move_lines) or any([x.state == 'draft' for x in pick.move_lines]):
move_lines = cr.fetchall() #x[0] = state, x[1] partially available
if (not move_lines) or any([x[0] == 'draft' for x in move_lines]):
res[pick.id] = 'draft' res[pick.id] = 'draft'
continue continue
if all([x[0] == 'cancel' for x in move_lines]): if all([x.state == 'cancel' for x in pick.move_lines]):
res[pick.id] = 'cancel' res[pick.id] = 'cancel'
continue continue
if all([x[0] in ('cancel', 'done') for x in move_lines]): if all([x.state in ('cancel', 'done') for x in pick.move_lines]):
res[pick.id] = 'done' res[pick.id] = 'done'
continue continue
order = {'confirmed': 0, 'waiting': 1, 'assigned': 2} order = {'confirmed': 0, 'waiting': 1, 'assigned': 2}
order_inv = {0: 'confirmed', 1: 'waiting', 2: 'assigned'} order_inv = {0: 'confirmed', 1: 'waiting', 2: 'assigned'}
lst = [order[x[0]] for x in move_lines if x[0] not in ('cancel', 'done')] lst = [order[x.state] for x in pick.move_lines if x.state not in ('cancel', 'done')]
if pick.move_type == 'one': if pick.move_type == 'one':
res[pick.id] = order_inv[min(lst)] res[pick.id] = order_inv[min(lst)]
else: else:
@ -702,8 +687,8 @@ class stock_picking(osv.osv):
res[pick.id] = order_inv[max(lst)] res[pick.id] = order_inv[max(lst)]
if not all(x == 2 for x in lst): if not all(x == 2 for x in lst):
#if all moves aren't assigned, check if we have one product partially available #if all moves aren't assigned, check if we have one product partially available
for move in move_lines: for move in pick.move_lines:
if move[1]: if move.partially_available:
res[pick.id] = 'partially_available' res[pick.id] = 'partially_available'
break break
return res return res
@ -957,6 +942,7 @@ class stock_picking(osv.osv):
""" """
# Try to find as much as possible top-level packages that can be moved # Try to find as much as possible top-level packages that can be moved
pack_obj = self.pool.get("stock.quant.package") pack_obj = self.pool.get("stock.quant.package")
quant_obj = self.pool.get("stock.quant")
top_lvl_packages = set() top_lvl_packages = set()
quants_to_compare = quants_suggested_locations.keys() quants_to_compare = quants_suggested_locations.keys()
for pack in list(set([x.package_id for x in quants_suggested_locations.keys() if x and x.package_id])): for pack in list(set([x.package_id for x in quants_suggested_locations.keys() if x and x.package_id])):
@ -965,9 +951,9 @@ class stock_picking(osv.osv):
good_pack = False good_pack = False
pack_destination = False pack_destination = False
while loop: while loop:
pack_quants = pack_obj.get_contents(cr, uid, test_pack, context=context) pack_quants = pack_obj.get_content(cr, uid, [test_pack.id], context=context)
all_in = True all_in = True
for quant in pack_quants: for quant in quant_obj.browse(cr, uid, pack_quants, context=context):
# If the quant is not in the quants to compare and not in the common location # If the quant is not in the quants to compare and not in the common location
if not quant in quants_to_compare: if not quant in quants_to_compare:
all_in = False all_in = False
@ -1030,13 +1016,14 @@ class stock_picking(osv.osv):
top_lvl_packages = self._get_top_level_packages(cr, uid, quants_suggested_locations, context=context) top_lvl_packages = self._get_top_level_packages(cr, uid, quants_suggested_locations, context=context)
# and then create pack operations for the top-level packages found # and then create pack operations for the top-level packages found
for pack in top_lvl_packages: for pack in top_lvl_packages:
pack_quants = pack_obj.get_contents(cr, uid, pack, context=context) pack_quants_ids = pack_obj.get_content(cr, uid, [pack.id], context=context)
pack_quants = quant_obj.browse(cr, uid, pack_quant_ids, context=context)
vals.append({ vals.append({
'picking_id': picking.id, 'picking_id': picking.id,
'package_id': pack.id, 'package_id': pack.id,
'product_qty': 1.0, 'product_qty': 1.0,
'location_id': pack.location_id.id, 'location_id': pack.location_id.id,
'location_dest_id': quants_suggested_locations[pack_quants[0]], 'location_dest_id': quants_suggested_locations[pack_quants[0]], context=context)],
}) })
#remove the quants inside the package so that they are excluded from the rest of the computation #remove the quants inside the package so that they are excluded from the rest of the computation
for quant in pack_quants: for quant in pack_quants:
@ -1076,12 +1063,12 @@ class stock_picking(osv.osv):
}) })
return vals return vals
def do_prepare_partial(self, cr, uid, picking_ids, context=None): def do_prepare_partial(self, cr, uid, picking_ids, context=None):
context = context or {} context = context or {}
pack_operation_obj = self.pool.get('stock.pack.operation')
#used to avoid recomputing the remaining quantities at each new pack operation created
ctx = context.copy() ctx = context.copy()
ctx['no_recompute'] = True ctx['no_recompute'] = True
pack_operation_obj = self.pool.get('stock.pack.operation')
#get list of existing operations and delete them #get list of existing operations and delete them
existing_package_ids = pack_operation_obj.search(cr, uid, [('picking_id', 'in', picking_ids)], context=context) existing_package_ids = pack_operation_obj.search(cr, uid, [('picking_id', 'in', picking_ids)], context=context)
@ -1102,11 +1089,11 @@ class stock_picking(osv.osv):
if forced_qties.get(move.product_id): if forced_qties.get(move.product_id):
forced_qties[move.product_id] += forced_qty forced_qties[move.product_id] += forced_qty
else: else:
forced_qties[move.product_id] = forced_qty forced_qties[move.product_id] = forced_qty
for vals in self._prepare_pack_ops(cr, uid, picking, picking_quants, forced_qties, context=context): for vals in self._prepare_pack_ops(cr, uid, picking, picking_quants, forced_qties, context=context):
pack_operation_obj.create(cr, uid, vals, context=context) pack_operation_obj.create(cr, uid, vals, context=ctx)
#recompute the remaining quantities all at once
self.do_recompute_remaining_quantities(cr, uid, picking_ids, context=context)
def do_unreserve(self, cr, uid, picking_ids, context=None): def do_unreserve(self, cr, uid, picking_ids, context=None):
""" """
@ -1140,8 +1127,8 @@ class stock_picking(osv.osv):
def _check_quants_reserved(ops): def _check_quants_reserved(ops):
if ops.package_id and not ops.product_id: if ops.package_id and not ops.product_id:
qty_op_rem[ops.id] = {} qty_op_rem[ops.id] = {}
package_obj.get_contents(cr, uid, ops.package_id) quant_ids = package_obj.get_content(cr, uid, [ops.package_id.id], context=context)
for quant in package_obj.get_contents(cr, uid, ops.package_id): for quant in quant_obj.browse(cr, uid, quant_ids, context=context):
if quant.id in quants_done.keys() and (quants_done[quant.id] == quant.qty): if quant.id in quants_done.keys() and (quants_done[quant.id] == quant.qty):
#Entire packages means entire quants from those packages #Entire packages means entire quants from those packages
if not quants_done.get(quant.id): if not quants_done.get(quant.id):
@ -1230,7 +1217,6 @@ class stock_picking(osv.osv):
quants_reserve_ok = all([quants_done[x] == 0 for x in quants_done.keys()]) quants_reserve_ok = all([quants_done[x] == 0 for x in quants_done.keys()])
return (quants_reserve_ok, remaining_qty_ok) return (quants_reserve_ok, remaining_qty_ok)
def do_recompute_remaining_quantities(self, cr, uid, picking_ids, context=None): def do_recompute_remaining_quantities(self, cr, uid, picking_ids, context=None):
quants_res = True quants_res = True
remaining_res = True remaining_res = True
@ -1241,9 +1227,6 @@ class stock_picking(osv.osv):
remaining_res = remaining_res and remaining_ok remaining_res = remaining_res and remaining_ok
return (quants_res, remaining_res) return (quants_res, remaining_res)
def _create_extra_moves(self, cr, uid, picking, context=None): def _create_extra_moves(self, cr, uid, picking, context=None):
'''This function creates move lines on a picking, at the time of do_transfer, based on '''This function creates move lines on a picking, at the time of do_transfer, based on
unexpected product transfers (or exceeding quantities) found in the pack operations. unexpected product transfers (or exceeding quantities) found in the pack operations.
@ -1277,7 +1260,6 @@ class stock_picking(osv.osv):
stock_move_obj.do_unreserve(cr, uid, move_ids, context=context) stock_move_obj.do_unreserve(cr, uid, move_ids, context=context)
stock_move_obj.action_assign(cr, uid, move_ids, context=context) stock_move_obj.action_assign(cr, uid, move_ids, context=context)
def do_transfer(self, cr, uid, picking_ids, context=None): def do_transfer(self, cr, uid, picking_ids, context=None):
""" """
If no pack operation, we do simple action_done of the picking If no pack operation, we do simple action_done of the picking
@ -1609,7 +1591,7 @@ class stock_move(osv.osv):
"* Waiting Availability: This state is reached when the procurement resolution is not straight forward. It may need the scheduler to run, a component to me manufactured...\n"\ "* Waiting Availability: This state is reached when the procurement resolution is not straight forward. It may need the scheduler to run, a component to me manufactured...\n"\
"* Available: When products are reserved, it is set to \'Available\'.\n"\ "* Available: When products are reserved, it is set to \'Available\'.\n"\
"* Done: When the shipment is processed, the state is \'Done\'."), "* Done: When the shipment is processed, the state is \'Done\'."),
'partially_available': fields.boolean('Partially Available', readonly = True, help = "Checks if the move has some stock reserved"), 'partially_available': fields.boolean('Partially Available', readonly=True, help="Checks if the move has some stock reserved"),
'price_unit': fields.float('Unit Price', help="Technical field used to record the product cost set by the user during a picking confirmation (when costing method used is 'average price' or 'real'). Value given in company currency and in product uom."), # as it's a technical field, we intentionally don't provide the digits attribute 'price_unit': fields.float('Unit Price', help="Technical field used to record the product cost set by the user during a picking confirmation (when costing method used is 'average price' or 'real'). Value given in company currency and in product uom."), # as it's a technical field, we intentionally don't provide the digits attribute
'company_id': fields.many2one('res.company', 'Company', required=True, select=True), 'company_id': fields.many2one('res.company', 'Company', required=True, select=True),
@ -1679,6 +1661,18 @@ class stock_move(osv.osv):
'propagate': True, 'propagate': True,
} }
def _check_uom(self, cr, uid, ids, context=None):
for move in self.browse(cr, uid, ids, context=context):
if move.product_id.uom_id.category_id.id != move.product_uom.category_id.id:
return False
return True
_constraints = [
(_check_uom,
'You try to move a product using a UoM that is not compatible with the UoM of the product moved. Please use an UoM in the same UoM category.',
['product_uom']),
]
def copy_data(self, cr, uid, id, default=None, context=None): def copy_data(self, cr, uid, id, default=None, context=None):
if default is None: if default is None:
default = {} default = {}
@ -1748,7 +1742,6 @@ class stock_move(osv.osv):
push_obj._apply(cr, uid, rule, move, context=context) push_obj._apply(cr, uid, rule, move, context=context)
return True return True
def _create_procurement(self, cr, uid, move, context=None): def _create_procurement(self, cr, uid, move, context=None):
""" This will create a procurement order """ """ This will create a procurement order """
return self.pool.get("procurement.order").create(cr, uid, self._prepare_procurement_from_move(cr, uid, move, context=context)) return self.pool.get("procurement.order").create(cr, uid, self._prepare_procurement_from_move(cr, uid, move, context=context))
@ -1761,17 +1754,6 @@ class stock_move(osv.osv):
# Check that we do not modify a stock.move which is done # Check that we do not modify a stock.move which is done
frozen_fields = set(['product_qty', 'product_uom', 'product_uos_qty', 'product_uos', 'location_id', 'location_dest_id', 'product_id']) frozen_fields = set(['product_qty', 'product_uom', 'product_uos_qty', 'product_uos', 'location_id', 'location_dest_id', 'product_id'])
for move in self.browse(cr, uid, ids, context=context): for move in self.browse(cr, uid, ids, context=context):
#Check UoM in meantime
if vals.get('product_uom') or vals.get('product_id'):
product_uom = move.product_id.uom_id
move_uom = move.product_uom
if vals.get('product_uom'):
move_uom = self.pool.get('product.uom').browse(cr, uid, vals['product_uom'], context=context)
if vals.get('product_id'):
product_uom = self.pool.get('product.product').browse(cr, uid, vals['product_id'], context=context).uom_id
if move_uom.category_id.id != product_uom.category_id.id:
raise osv.except_osv(_('Operation Forbidden'),
_('Category of Product UoM must be the same as the category of the UoM of the move'))
if move.state == 'done': if move.state == 'done':
if frozen_fields.intersection(vals): if frozen_fields.intersection(vals):
raise osv.except_osv(_('Operation Forbidden!'), raise osv.except_osv(_('Operation Forbidden!'),
@ -2113,11 +2095,11 @@ class stock_move(osv.osv):
self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}, context=context) self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}, context=context)
return self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}, context=context) return self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}, context=context)
def _check_package_from_moves(self, cr, uid, moves, context=None): def _check_package_from_moves(self, cr, uid, ids, context=None):
pack_obj = self.pool.get("stock.quant.package") pack_obj = self.pool.get("stock.quant.package")
packs = set() packs = set()
for move in moves: for move in self.browse(cr, uid, ids, context=context):
packs |= set([q.package_id for q in move.quant_ids if q.package_id and q.qty > 0]) packs |= set([q.package_id.id for q in move.quant_ids if q.package_id and q.qty > 0])
return pack_obj._check_location_constraint(cr, uid, list(packs), context=context) return pack_obj._check_location_constraint(cr, uid, list(packs), context=context)
def action_done(self, cr, uid, ids, context=None): def action_done(self, cr, uid, ids, context=None):
@ -2140,7 +2122,6 @@ class stock_move(osv.osv):
for link in move.linked_move_operation_ids: for link in move.linked_move_operation_ids:
operations.add(link.operation_id) operations.add(link.operation_id)
#Sort operations according to entire packages first, then package + lot, package only, lot only #Sort operations according to entire packages first, then package + lot, package only, lot only
operations = list(operations) operations = list(operations)
operations.sort(key = lambda x: ((x.package_id and not x.product_id) and -4 or 0) + (x.package_id and -2 or 0) + (x.lot_id and -1 or 0)) operations.sort(key = lambda x: ((x.package_id and not x.product_id) and -4 or 0) + (x.package_id and -2 or 0) + (x.lot_id and -1 or 0))
@ -2157,16 +2138,20 @@ class stock_move(osv.osv):
dom = main_domain + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context) dom = main_domain + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context)
quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id, move.product_id, record.qty, domain=dom, prefered_domain=prefered_domain, quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id, move.product_id, record.qty, domain=dom, prefered_domain=prefered_domain,
fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
package_id = False
if not ops.product_id and ops.package_id: if ops.result_package_id.id:
#if a package and a result_package is given, we will put package_id, otherwise it will be result_pakege #if a result package is given, all quants go there
#but for operations having only result_package_id, we will create new quants in the final package directly quant_dest_package_id = ops.result_package_id.id
package_id = ops.package_id.id elif ops.product_id and ops.package_id:
#if a package and a product is given, we will remove quants from the pack.
quant_dest_package_id = False
else: else:
package_id = ops.result_package_id.id #otherwise we keep the current pack of the quant, which may mean None
quant_obj.quants_move(cr, uid, quants, move, ops.location_dest_id, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=package_id, context=context) quant_dest_package_id = ops.package_id.id
quant_obj.quants_move(cr, uid, quants, move, ops.location_dest_id, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=quant_dest_package_id, context=context)
# Handle pack in pack # Handle pack in pack
pack_op_obj.process_packaging(cr, uid, ops, context=context) if not ops.product_id and ops.package_id and ops.result_package_id.id != ops.package_id.parent_id.id:
pack_obj.write(cr, SUPERUSER_ID, [ops.package_id.id], {'result_package_id': ops.result_package_id.id}, context=context)
move_qty[move.id] -= record.qty move_qty[move.id] -= record.qty
#Check for remaining qtys and unreserve/check move_dest_id in #Check for remaining qtys and unreserve/check move_dest_id in
for move in self.browse(cr, uid, ids, context=context): for move in self.browse(cr, uid, ids, context=context):
@ -2192,7 +2177,7 @@ class stock_move(osv.osv):
procurement_ids.append(move.procurement_id.id) procurement_ids.append(move.procurement_id.id)
# Check the packages have been placed in the correct locations # Check the packages have been placed in the correct locations
self._check_package_from_moves(cr, uid, self.browse(cr, uid, ids, context=context), context=context) self._check_package_from_moves(cr, uid, ids, context=context)
# Apply on picking # Apply on picking
done_date = context.get('force_date', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)) done_date = context.get('force_date', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))
self.write(cr, uid, ids, {'state': 'done', 'date': done_date}, context=context) self.write(cr, uid, ids, {'state': 'done', 'date': done_date}, context=context)
@ -3469,16 +3454,18 @@ class stock_package(osv.osv):
'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').get(cr, uid, 'stock.quant.package') or _('Unknown Pack') 'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').get(cr, uid, 'stock.quant.package') or _('Unknown Pack')
} }
def _check_location_constraint(self, cr, uid, packs, context=None): def _check_location_constraint(self, cr, uid, ids, context=None):
'''checks that all quants in a package are stored in the same location. This function cannot be used '''checks that all quants in a package are stored in the same location. This function cannot be used
as a constraint because it needs to be checked on pack operations (they may not call write on the as a constraint because it needs to be checked on pack operations (they may not call write on the
package) package)
''' '''
for pack in packs: quant_obj = self.pool.get('stock.quant')
for pack in self.browse(cr, uid, ids, context=context):
parent = pack parent = pack
while parent.parent_id: while parent.parent_id:
parent = parent.parent_id parent = parent.parent_id
quants = self.get_contents(cr, uid, parent, context=context) quant_ids = self.get_content(cr, uid, [parent.id], context=context)
quants = quant_obj.browse(cr, uid, quant_ids, context=context)
location_id = quants and quants[0].location_id.id or False location_id = quants and quants[0].location_id.id or False
if not all([quant.location_id.id == location_id for quant in quants if quant.qty > 0]): if not all([quant.location_id.id == location_id for quant in quants if quant.qty > 0]):
raise osv.except_osv(_('Error'), _('Everything inside a package should be in the same location')) raise osv.except_osv(_('Error'), _('Everything inside a package should be in the same location'))
@ -3513,17 +3500,6 @@ class stock_package(osv.osv):
child_package_ids = self.search(cr, uid, [('id', 'child_of', ids)], context=context) child_package_ids = self.search(cr, uid, [('id', 'child_of', ids)], context=context)
return self.pool.get('stock.quant').search(cr, uid, [('package_id', 'in', child_package_ids)], context=context) return self.pool.get('stock.quant').search(cr, uid, [('package_id', 'in', child_package_ids)], context=context)
def get_contents(self, cr, uid, pack, context=None):
quants = pack.quant_ids
children_ids = pack.children_ids
while children_ids:
children2_ids = []
for child in children_ids:
quants += child.quant_ids
children2_ids += child.children_ids
children_ids = children2_ids
return quants
def get_content_package(self, cr, uid, ids, context=None): def get_content_package(self, cr, uid, ids, context=None):
quants_ids = self.get_content(cr, uid, ids, context=context) quants_ids = self.get_content(cr, uid, ids, context=context)
res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'stock', 'quantsact', context=context) res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'stock', 'quantsact', context=context)
@ -3587,7 +3563,6 @@ class stock_pack_operation(osv.osv):
res[record.move_id.product_id.id] -= record.qty res[record.move_id.product_id.id] -= record.qty
return res return res
def _get_remaining_qty(self, cr, uid, ids, name, args, context=None): def _get_remaining_qty(self, cr, uid, ids, name, args, context=None):
uom_obj = self.pool.get('product.uom') uom_obj = self.pool.get('product.uom')
res = {} res = {}
@ -3666,12 +3641,8 @@ class stock_pack_operation(osv.osv):
if isinstance(ids, (int, long)): if isinstance(ids, (int, long)):
ids = [ids] ids = [ids]
if not context.get("no_recompute"): if not context.get("no_recompute"):
if vals.get('picking_id'): pickings = vals.get('picking_id') and [vals['picking_id]] or list(set([x.picking_id.id for x in self.browse(cr, uid, ids, context=context)]))
self.pool.get("stock.picking").do_recompute_remaining_quantities(cr, uid, [vals['picking_id']], context=context) self.pool.get("stock.picking").do_recompute_remaining_quantities(cr, uid, pickings, context=context)
else:
moves = self.browse(cr, uid, ids, context=context)
pickings = list(set([x.picking_id.id for x in moves]))
self.pool.get("stock.picking").do_recompute_remaining_quantities(cr, uid, pickings, context=context)
return res return res
def create(self, cr, uid, vals, context=None): def create(self, cr, uid, vals, context=None):
@ -3680,17 +3651,6 @@ class stock_pack_operation(osv.osv):
if vals.get("picking_id") and not context.get("no_recompute"): if vals.get("picking_id") and not context.get("no_recompute"):
self.pool.get("stock.picking").do_recompute_remaining_quantities(cr, uid, [vals['picking_id']], context=context) self.pool.get("stock.picking").do_recompute_remaining_quantities(cr, uid, [vals['picking_id']], context=context)
return res_id return res_id
def process_packaging(self, cr, uid, operation, context=None):
''' Process the packaging of a given operation, after the quants have been moved. If there was not enough quants found
a quant already has been with the good package information so we don't consider that case in this method'''
pack_obj = self.pool.get("stock.quant.package")
if not operation.product_id and operation.package_id and operation.result_package_id.id != operation.package_id.parent_id.id:
pack_obj.write(cr, SUPERUSER_ID, [operation.package_id.id], {'result_package_id': operation.result_package_id.id}, context=context)
#TODO: this function can be refactored #TODO: this function can be refactored
def _search_and_increment(self, cr, uid, picking_id, domain, context=None): def _search_and_increment(self, cr, uid, picking_id, domain, context=None):

View File

@ -77,33 +77,35 @@ class stock_quant(osv.osv):
if quant.product_id.cost_method == 'real' and quant.location_id.usage != 'internal': if quant.product_id.cost_method == 'real' and quant.location_id.usage != 'internal':
self.pool.get('stock.move')._store_average_cost_price(cr, uid, move, context=context) self.pool.get('stock.move')._store_average_cost_price(cr, uid, move, context=context)
"""
Accounting Valuation Entries
quants: Quants to create accounting valuation entries for
move: Move to use
"""
def _account_entry_move(self, cr, uid, quants, move, context=None): def _account_entry_move(self, cr, uid, quants, move, context=None):
location_from = move.location_id """
location_to = quants[0].location_id Accounting Valuation Entries
quants: browse record list of Quants to create accounting valuation entries for. Unempty and all quants are supposed to have the same location id (thay already moved in)
move: Move to use. browse record
"""
if context is None: if context is None:
context = {} context = {}
if quants[0].product_id.valuation != 'real_time': location_obj = self.pool.get('stock.location')
return False location_from = move.location_id
if quants[0].owner_id: location_to = quants[0].location_id
#if the quant isn't owned by the company, we don't make any valuation entry company_from = location_obj._location_owner(cr, uid, location_from, context=context)
return False company_to = location_obj._location_owner(cr, uid, location_to, context=context)
if quants[0].qty <= 0:
#we don't make any stock valuation for negative quants because the valuation is already made for the counterpart.
#At that time the valuation will be made at the product cost price and afterward there will be new accounting entries
#to make the adjustments when we know the real cost price.
return False
company_from = self._location_owner(cr, uid, quants[0], location_from, context=context)
company_to = self._location_owner(cr, uid, quants[0], location_to, context=context)
if company_from == company_to: if company_from == company_to:
return False return False
if move.product_id.valuation != 'real_time':
return False
for q in quants:
if q.owner_id:
#if the quant isn't owned by the company, we don't make any valuation entry
return False
if q.qty <= 0:
#we don't make any stock valuation for negative quants because the valuation is already made for the counterpart.
#At that time the valuation will be made at the product cost price and afterward there will be new accounting entries
#to make the adjustments when we know the real cost price.
return False
# Create Journal Entry for products arriving in the company # Create Journal Entry for products arriving in the company
if company_to: if company_to:
ctx = context.copy() ctx = context.copy()
@ -126,19 +128,17 @@ class stock_quant(osv.osv):
else: else:
self._create_account_move_line(cr, uid, quants, move, acc_valuation, acc_dest, journal_id, context=ctx) self._create_account_move_line(cr, uid, quants, move, acc_valuation, acc_dest, journal_id, context=ctx)
def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, force_location=False, context=None): def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, force_location=False, context=None):
quant = super(stock_quant, self)._quant_create(cr, uid, qty, move, lot_id, owner_id, src_package_id, dest_package_id, force_location, context=context) quant = super(stock_quant, self)._quant_create(cr, uid, qty, move, lot_id, owner_id, src_package_id, dest_package_id, force_location, context=context)
if move.product_id.valuation == 'real_time': if move.product_id.valuation == 'real_time':
self._account_entry_move(cr, uid, [quant], move, context) self._account_entry_move(cr, uid, [quant], move, context)
return quant return quant
def move_single_quant_tuples(self, cr, uid, quants, move, location_dest_id, dest_package_id, context=None): def move_quants_write(self, cr, uid, quants, move, location_dest_id, dest_package_id, context=None):
quant_record = super(stock_quant, self).move_single_quant_tuples(cr, uid, quants, move, location_dest_id, dest_package_id, context=context) res = super(stock_quant, self).move_quants_write(cr, uid, quants, move, location_dest_id, dest_package_id, context=context)
if move.product_id.valuation == 'real_time': if move.product_id.valuation == 'real_time':
quants_filt = [x[0] for x in quants] self._account_entry_move(cr, uid, quants, move, context=context)
self._account_entry_move(cr, uid, quants_filt, move, context=context) return res
return quant_record
def _get_accounting_data_for_valuation(self, cr, uid, move, context=None): def _get_accounting_data_for_valuation(self, cr, uid, move, context=None):
@ -190,7 +190,6 @@ class stock_quant(osv.osv):
#the company currency... so we need to use round() before creating the accounting entries. #the company currency... so we need to use round() before creating the accounting entries.
valuation_amount = currency_obj.round(cr, uid, move.company_id.currency_id, valuation_amount * qty) valuation_amount = currency_obj.round(cr, uid, move.company_id.currency_id, valuation_amount * qty)
partner_id = (move.picking_id.partner_id and self.pool.get('res.partner')._find_accounting_partner(move.picking_id.partner_id).id) or False partner_id = (move.picking_id.partner_id and self.pool.get('res.partner')._find_accounting_partner(move.picking_id.partner_id).id) or False
debit_line_vals = { debit_line_vals = {
'name': move.name, 'name': move.name,
'product_id': move.product_id.id, 'product_id': move.product_id.id,
@ -219,20 +218,18 @@ class stock_quant(osv.osv):
def _create_account_move_line(self, cr, uid, quants, move, credit_account_id, debit_account_id, journal_id, context=None): def _create_account_move_line(self, cr, uid, quants, move, credit_account_id, debit_account_id, journal_id, context=None):
#group quants by cost #group quants by cost
quant_cost = {}
quant_cost_qty = {} quant_cost_qty = {}
for quant in quants: for quant in quants:
if quant_cost.get(quant.cost): if quant_cost_qty.get(quant.cost):
quant_cost_qty[quant.cost] += quant.qty quant_cost_qty[quant.cost] += quant.qty
else: else:
quant_cost[quant.cost] = quant
quant_cost_qty[quant.cost] = quant.qty quant_cost_qty[quant.cost] = quant.qty
move_obj = self.pool.get('account.move') move_obj = self.pool.get('account.move')
for cost in quant_cost_qty.keys(): for cost, qty in quant_cost_qty.items():
move_lines = self._prepare_account_move_line(cr, uid, move, quant_cost_qty[cost], cost, credit_account_id, debit_account_id, context=context) move_lines = self._prepare_account_move_line(cr, uid, move, qty, cost, credit_account_id, debit_account_id, context=context)
return move_obj.create(cr, uid, {'journal_id': journal_id, return move_obj.create(cr, uid, {'journal_id': journal_id,
'line_id': move_lines, 'line_id': move_lines,
'perdiod_id': self.pool.get('account.period').find(cr, uid, move.date, context=context)[0], 'period_id': self.pool.get('account.period').find(cr, uid, move.date, context=context)[0],
'date': move.date, 'date': move.date,
'ref': move.picking_id and move.picking_id.name}, context=context) 'ref': move.picking_id and move.picking_id.name}, context=context)