[MERGE] merged the branch with lot handling on mrp.production and some refactoring in stock as well (action_consume moved to mrp, split/merge inventory removed..)

bzr revid: qdp-launchpad@openerp.com-20140205133031-83q48ftnr0iid3r9
This commit is contained in:
Quentin (OpenERP) 2014-02-05 14:30:31 +01:00
commit e25d1369a0
21 changed files with 464 additions and 855 deletions

View File

@ -63,6 +63,7 @@ Dashboard / Reports for MRP will include:
'wizard/change_production_qty_view.xml',
'wizard/mrp_price_view.xml',
'wizard/mrp_workcenter_load_view.xml',
'wizard/stock_move_view.xml',
'mrp_view.xml',
'mrp_report.xml',
'company_view.xml',

View File

@ -365,8 +365,7 @@ class mrp_bom(osv.osv):
phantom = False
if not phantom:
if addthis and not bom.bom_lines:
result.append(
{
result.append({
'name': bom.product_id.name,
'product_id': bom.product_id.id,
'product_qty': bom.product_qty * factor,
@ -472,7 +471,6 @@ class mrp_production(osv.osv):
res[production.id] = False
return res
def _mrp_from_move(self, cr, uid, ids, context=None):
""" Return mrp"""
res = []
@ -480,8 +478,6 @@ class mrp_production(osv.osv):
res += self.pool.get("mrp.production").search(cr, uid, [('move_lines', 'in', move.id)], context=context)
return res
_columns = {
'name': fields.char('Reference', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
'origin': fields.char('Source Document', size=64, readonly=True, states={'draft': [('readonly', False)]},
@ -514,13 +510,13 @@ class mrp_production(osv.osv):
'move_lines': fields.one2many('stock.move', 'raw_material_production_id', 'Products to Consume',
domain=[('state', 'not in', ('done', 'cancel'))], readonly=True, states={'draft': [('readonly', False)]}),
'move_lines2': fields.one2many('stock.move', 'raw_material_production_id', 'Consumed Products',
domain=[('state','in', ('done', 'cancel'))], readonly=True, states={'draft':[('readonly',False)]}),
domain=[('state', 'in', ('done', 'cancel'))], readonly=True),
'move_created_ids': fields.one2many('stock.move', 'production_id', 'Products to Produce',
domain=[('state','not in', ('done', 'cancel'))], readonly=True, states={'draft':[('readonly',False)]}),
domain=[('state', 'not in', ('done', 'cancel'))], readonly=True),
'move_created_ids2': fields.one2many('stock.move', 'production_id', 'Produced Products',
domain=[('state','in', ('done', 'cancel'))], readonly=True, states={'draft':[('readonly',False)]}),
domain=[('state', 'in', ('done', 'cancel'))], readonly=True),
'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods',
readonly=True, states={'draft':[('readonly',False)]}),
readonly=True),
'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Work Centers Utilisation',
readonly=True, states={'draft': [('readonly', False)]}),
'state': fields.selection(
@ -539,8 +535,8 @@ class mrp_production(osv.osv):
'user_id': fields.many2one('res.users', 'Responsible'),
'company_id': fields.many2one('res.company', 'Company', required=True),
'ready_production': fields.function(_moves_assigned, type='boolean', store={'stock.move': (_mrp_from_move, ['state'], 10)}),
}
_defaults = {
'priority': lambda *a: '1',
'state': lambda *a: 'draft',
@ -552,10 +548,12 @@ class mrp_production(osv.osv):
'location_src_id': _src_id_default,
'location_dest_id': _dest_id_default
}
_sql_constraints = [
('name_uniq', 'unique(name, company_id)', 'Reference must be unique per Company!'),
]
_order = 'priority desc, date_planned asc';
_order = 'priority desc, date_planned asc'
def _check_qty(self, cr, uid, ids, context=None):
for order in self.browse(cr, uid, ids, context=context):
@ -729,7 +727,7 @@ class mrp_production(osv.osv):
for production in self.browse(cr, uid, ids, context=context):
if not production.move_created_ids:
produce_move_id = self._make_production_produce_line(cr, uid, production, context=context)
self._make_production_produce_line(cr, uid, production, context=context)
for scheduled in production.product_lines:
self._make_production_line_procurement(cr, uid, scheduled, False, context=context)
@ -771,31 +769,20 @@ class mrp_production(osv.osv):
"""
return 1
def action_produce(self, cr, uid, production_id, production_qty, production_mode, context=None):
""" To produce final product based on production mode (consume/consume&produce).
If Production mode is consume, all stock move lines of raw materials will be done/consumed.
If Production mode is consume & produce, all stock move lines of raw materials will be done/consumed
and stock move lines of final product will be also done/produced.
@param production_id: the ID of mrp.production object
@param production_qty: specify qty to produce
@param production_mode: specify production mode (consume/consume&produce).
@return: True
"""
stock_mov_obj = self.pool.get('stock.move')
production = self.browse(cr, uid, production_id, context=context)
if not production.move_lines and production.state == 'ready':
# trigger workflow if not products to consume (eg: services)
self.signal_button_produce(cr, uid, [production_id])
def _get_produced_qty(self, cr, uid, production, context=None):
''' returns the produced quantity of product 'production.product_id' for the given production, in the product UoM
'''
produced_qty = 0
for produced_product in production.move_created_ids2:
if (produced_product.scrapped) or (produced_product.product_id.id != production.product_id.id):
continue
produced_qty += produced_product.product_qty
if production_mode in ['consume','consume_produce']:
consumed_data = {}
return produced_qty
def _get_consumed_data(self, cr, uid, production, context=None):
''' returns a dictionary containing for each raw material of the given production, its quantity already consumed (in the raw material UoM)
'''
consumed_data = {}
# Calculate already consumed qtys
for consumed in production.move_lines2:
if consumed.scrapped:
@ -803,34 +790,90 @@ class mrp_production(osv.osv):
if not consumed_data.get(consumed.product_id.id, False):
consumed_data[consumed.product_id.id] = 0
consumed_data[consumed.product_id.id] += consumed.product_qty
return consumed_data
def _calculate_qty(self, cr, uid, production, product_qty=0.0, context=None):
"""
Calculates the quantity still needed to produce an extra number of products
"""
quant_obj = self.pool.get("stock.quant")
produced_qty = self._get_produced_qty(cr, uid, production, context=context)
consumed_data = self._get_consumed_data(cr, uid, production, context=context)
#In case no product_qty is given, take the remaining qty to produce for the given production
if not product_qty:
product_qty = production.product_qty - produced_qty
dicts = {}
# Find product qty to be consumed and consume it
for scheduled in production.product_lines:
# total qty of consumed product we need after this consumption
total_consume = ((production_qty + produced_qty) * scheduled.product_qty / production.product_qty)
consumed_qty = consumed_data.get(scheduled.product_id.id, 0.0)
# qty available for consume and produce
qty_avail = scheduled.product_qty - consumed_data.get(scheduled.product_id.id, 0.0)
if float_compare(qty_avail, 0, precision_rounding=scheduled.product_id.uom_id.rounding) <= 0:
qty_avail = scheduled.product_qty - consumed_qty
if qty_avail <= 0.0:
# there will be nothing to consume for this raw material
continue
raw_product = [move for move in production.move_lines if move.product_id.id==scheduled.product_id.id]
if raw_product:
# qtys we have to consume
qty = total_consume - consumed_data.get(scheduled.product_id.id, 0.0)
if float_compare(qty, qty_avail, precision_rounding=scheduled.product_id.uom_id.rounding) == 1:
# if qtys we have to consume is more than qtys available to consume
prod_name = scheduled.product_id.name_get()[0][1]
raise osv.except_osv(_('Warning!'), _('You are going to consume total %s quantities of "%s".\nBut you can only consume up to total %s quantities.') % (qty, prod_name, qty_avail))
if float_compare(qty, 0, precision_rounding=scheduled.product_id.uom_id.rounding) <= 0:
# we already have more qtys consumed than we need
if not dicts.get(scheduled.product_id.id):
dicts[scheduled.product_id.id] = {}
# total qty of consumed product we need after this consumption
total_consume = ((product_qty + produced_qty) * scheduled.product_qty / production.product_qty)
qty = total_consume - consumed_qty
# Search for quants related to this related move
for move in production.move_lines:
if qty <= 0.0:
break
if move.product_id.id != scheduled.product_id.id:
continue
product_id = scheduled.product_id.id
raw_product[0].action_consume(qty, raw_product[0].location_id.id, context=context)
q = min(move.product_qty, qty)
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, scheduled.product_id, q, domain=[('qty', '>', 0.0)],
prefered_domain=[('reservation_id', '=', move.id)], fallback_domain=[('reservation_id', '=', False)], context=context)
for quant, quant_qty in quants:
if quant:
lot_id = quant.lot_id.id
if not product_id in dicts.keys():
dicts[product_id] = {lot_id: quant_qty}
elif lot_id in dicts[product_id].keys():
dicts[product_id][lot_id] += quant_qty
else:
dicts[product_id][lot_id] = quant_qty
qty -= quant_qty
if qty > 0:
if dicts[product_id].get(False):
dicts[product_id][False] += qty
else:
dicts[product_id][False] = qty
consume_lines = []
for prod in dicts.keys():
for lot, qty in dicts[prod].items():
consume_lines.append({'product_id': prod, 'product_qty': qty, 'lot_id': lot})
return consume_lines
def action_produce(self, cr, uid, production_id, production_qty, production_mode, wiz=False, context=None):
""" To produce final product based on production mode (consume/consume&produce).
If Production mode is consume, all stock move lines of raw materials will be done/consumed.
If Production mode is consume & produce, all stock move lines of raw materials will be done/consumed
and stock move lines of final product will be also done/produced.
@param production_id: the ID of mrp.production object
@param production_qty: specify qty to produce
@param production_mode: specify production mode (consume/consume&produce).
@param wiz: the mrp produce product wizard, which will tell the amount of consumed products needed
@return: True
"""
stock_mov_obj = self.pool.get('stock.move')
production = self.browse(cr, uid, production_id, context=context)
if not production.move_lines and production.state == 'ready':
# trigger workflow if not products to consume (eg: services)
self.signal_button_produce(cr, uid, [production_id])
produced_qty = self._get_produced_qty(cr, uid, production, context=context)
main_production_move = False
if production_mode == 'consume_produce':
# To produce remaining qty of final product
#vals = {'state':'confirmed'}
@ -849,12 +892,34 @@ class mrp_production(osv.osv):
produced_qty = produced_products.get(produce_product.product_id.id, 0)
subproduct_factor = self._get_subproduct_factor(cr, uid, production.id, produce_product.id, context=context)
rest_qty = (subproduct_factor * production.product_qty) - produced_qty
if rest_qty < (subproduct_factor * production_qty):
if float_compare(rest_qty, (subproduct_factor * production_qty), precision_rounding=produce_product.product_id.uom_id.rounding) < 0:
prod_name = produce_product.product_id.name_get()[0][1]
raise osv.except_osv(_('Warning!'), _('You are going to produce total %s quantities of "%s".\nBut you can only produce up to total %s quantities.') % ((subproduct_factor * production_qty), prod_name, rest_qty))
if rest_qty > 0 :
stock_mov_obj.action_consume(cr, uid, [produce_product.id], (subproduct_factor * production_qty), context=context)
if float_compare(rest_qty, 0, precision_rounding=produce_product.product_id.uom_id.rounding) > 0:
lot_id = False
if wiz:
lot_id = wiz.lot_id.id
new_moves = stock_mov_obj.action_consume(cr, uid, [produce_product.id], (subproduct_factor * production_qty), location_id=produce_product.location_id.id, restrict_lot_id=lot_id, context=context)
if produce_product.product_id.id == production.product_id.id and new_moves:
main_production_move = new_moves[0]
if production_mode in ['consume', 'consume_produce']:
if wiz:
consume_lines = []
for cons in wiz.consume_lines:
consume_lines.append({'product_id': cons.product_id.id, 'lot_id': cons.lot_id.id, 'product_qty': cons.product_qty})
else:
consume_lines = self._calculate_qty(cr, uid, production, production_qty, context=context)
for consume in consume_lines:
remaining_qty = consume['product_qty']
for raw_material_line in production.move_lines:
if remaining_qty <= 0:
break
if consume['product_id'] != raw_material_line.product_id.id:
continue
consumed_qty = min(remaining_qty, raw_material_line.product_qty)
stock_mov_obj.action_consume(cr, uid, [raw_material_line.id], consumed_qty, raw_material_line.location_id.id, restrict_lot_id=consume['lot_id'], consumed_for=main_production_move, context=context)
remaining_qty -= consumed_qty
self.message_post(cr, uid, production_id, body=_("%s produced") % self._description, context=context)
self.signal_button_produce_done(cr, uid, [production_id])
@ -991,7 +1056,6 @@ class mrp_production(osv.osv):
'product_uos': production_line.product_uos and production_line.product_uos.id or False,
'location_id': source_location_id,
'location_dest_id': destination_location_id,
'move_dest_id': parent_move_id,
'company_id': production.company_id.id,
'procure_method': 'make_to_order',
})
@ -1023,7 +1087,6 @@ class mrp_production(osv.osv):
@param *args: Arguments
@return: True
"""
move_obj = self.pool.get('stock.move')
for order in self.browse(cr, uid, ids):
move_obj.force_assign(cr, uid, [x.id for x in order.move_lines])

View File

@ -725,19 +725,12 @@
<field name="product_id"/>
<field name="product_qty" string="Quantity"/>
<field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
<field name="restrict_lot_id"/>
<field name="restrict_partner_id"/> <!-- TODO group on lot and ownerhsip feature -->
<field name="state" invisible="1"/>
<button name="%(stock.move_consume)d"
<button name="%(mrp.move_consume)d"
string="Consume Products" type="action"
icon="gtk-go-forward" context="{'consume': True}"
states="draft,waiting,confirmed,assigned"/>
<button
name="%(stock.track_line)d"
string="Split in Serial Numbers"
type="action" icon="gtk-justify-fill"
states="draft,waiting,confirmed,assigned"
groups="stock.group_tracking_lot"/>
states="assigned"
/>
<button name="%(stock.move_scrap)d"
string="Scrap Products" type="action"
icon="terp-gtk-jump-to-ltr" context="{'scrap': True}"
@ -749,9 +742,9 @@
<field name="move_lines2" nolabel="1" options="{'reload_on_button': true}">
<tree colors="red:scrapped==True;blue:state == 'draft';black:state in('picking_except','confirmed','ready','in_production');gray:state == 'cancel' " string="Consumed Products" editable="bottom">
<field name="product_id" readonly="1"/>
<field name="restrict_lot_id" context="{'product_id': product_id}" groups="stock.group_tracking_lot"/>
<field name="product_qty" readonly="1"/>
<field name="product_uom" readonly="1" string="Unit of Measure" groups="product.group_uom"/>
<!--<field name="prodlot_id" context="{'product_id': product_id}" groups="stock.group_tracking_lot"/>-->
<field name="state" invisible="1"/>
<field name="scrapped" invisible="1"/>
</tree>
@ -777,13 +770,11 @@
<tree colors="red:scrapped==True;blue:state == 'draft';black:state in('picking_except','confirmed','ready','in_production');gray:state in('cancel','done') " string="Finished Products">
<field name="product_id" readonly="1"/>
<field name="product_qty" readonly="1"/>
<field name="restrict_lot_id" groups="stock.group_tracking_lot"/>
<field name="product_uom" readonly="1" string="Unit of Measure" groups="product.group_uom"/>
<field name="location_dest_id" readonly="1" string="Destination Loc." widget="selection" groups="stock.group_locations"/>
<!--<field name="prodlot_id" context="{'product_id': product_id}" groups="stock.group_tracking_lot"/>-->
<field name="scrapped" invisible="1"/>
<field name="state" invisible="1"/>
<button name="%(stock.track_line)d"
string="Split in Serial Numbers" type="action" icon="gtk-justify-fill" states="done,cancel"/>
<button name="%(stock.move_scrap)d"
string="Scrap Products" type="action" icon="terp-gtk-jump-to-ltr"
states="done,cancel"/>

View File

@ -30,12 +30,15 @@ class StockMove(osv.osv):
_columns = {
'production_id': fields.many2one('mrp.production', 'Production Order for Produced Products', select=True),
'raw_material_production_id': fields.many2one('mrp.production', 'Production Order for Raw Materials', select=True),
'consumed_for': fields.many2one('stock.move', 'Consumed for', help='Technical field used to make the traceability of produced products'),
}
def check_tracking(self, cr, uid, move, lot_id, context=None):
super(StockMove, self).check_tracking(cr, uid, move, lot_id, context=context)
if move.product_id.track_production and (move.location_id.usage == 'production' or move.location_dest_id.usage == 'production') and not lot_id:
raise osv.except_osv(_('Warning!'), _('You must assign a serial number for the product %s') % (move.product_id.name))
if move.raw_material_production_id and move.location_dest_id.usage == 'production' and move.raw_material_production_id.product_id.track_production and not move.consumed_for:
raise osv.except_osv(_('Warning!'), _("Because the product %s requires it, you must assign a serial number to your raw material %s to proceed further in your production. Please use the 'Produce' button to do so.") % (move.raw_material_production_id.product_id.name, move.product_id.name))
def _action_explode(self, cr, uid, move, context=None):
""" Explodes pickings.
@ -100,29 +103,52 @@ class StockMove(osv.osv):
procurement_obj.signal_button_wait_done(cr, uid, procurement_ids)
return processed_ids
def action_consume(self, cr, uid, ids, product_qty, location_id=False, restrict_lot_id=False, restrict_partner_id=False, context=None):
""" Consumed product with specific quatity from specific source location.
def action_consume(self, cr, uid, ids, product_qty, location_id=False, restrict_lot_id=False, restrict_partner_id=False,
consumed_for=False, context=None):
""" Consumed product with specific quantity from specific source location.
@param product_qty: Consumed product quantity
@param location_id: Source location
@param restrict_lot_id: optionnal parameter that allows to restrict the choice of quants on this specific lot
@param restrict_partner_id: optionnal parameter that allows to restrict the choice of quants to this specific partner
@param consumed_for: optionnal parameter given to this function to make the link between raw material consumed and produced product, for a better traceability
@return: Consumed lines
"""
if context is None:
context = {}
res = []
production_obj = self.pool.get('mrp.production')
uom_obj = self.pool.get('product.uom')
if product_qty <= 0:
raise osv.except_osv(_('Warning!'), _('Please provide proper quantity.'))
for move in self.browse(cr, uid, ids, context=context):
if move.state == 'draft':
self.action_confirm(cr, uid, [move.id], context=context)
new_moves = super(StockMove, self).action_consume(cr, uid, [move.id], product_qty, location_id, restrict_lot_id=restrict_lot_id,
restrict_partner_id=restrict_partner_id, context=context)
move_qty = move.product_qty
uom_qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, product_qty, move.product_uom.id)
if move_qty <= 0:
raise osv.except_osv(_('Error!'), _('Cannot consume a move with negative or zero quantity.'))
quantity_rest = move.product_qty - uom_qty
if quantity_rest > 0:
ctx = context.copy()
if location_id:
ctx['source_location_id'] = location_id
new_mov = self.split(cr, uid, move, move_qty - quantity_rest, restrict_lot_id=restrict_lot_id, restrict_partner_id=restrict_partner_id, context=ctx)
self.write(cr, uid, new_mov, {'consumed_for': consumed_for}, context=context)
res.append(new_mov)
else:
res.append(move.id)
if location_id:
self.write(cr, uid, [move.id], {'location_id': location_id, 'restrict_lot_id': restrict_lot_id,
'restrict_partner_id': restrict_partner_id,
'consumed_for': consumed_for}, context=context)
self.action_done(cr, uid, res, context=context)
production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
for prod in production_obj.browse(cr, uid, production_ids, context=context):
if prod.state == 'confirmed':
production_obj.force_production(cr, uid, [prod.id])
production_obj.signal_button_produce(cr, uid, production_ids)
for new_move in new_moves:
if new_move == move.id:
#This move is already there in move lines of production order
continue
for new_move in res:
if new_move != move.id:
#This move is not already there in move lines of production order
production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new_move)]})
res.append(new_move)
return res
def action_scrap(self, cr, uid, ids, product_qty, location_id, restrict_lot_id=False, restrict_partner_id=False, context=None):
@ -156,6 +182,7 @@ class StockMove(osv.osv):
workflow.trg_trigger(uid, 'stock.move', move.id, cr)
return res
class StockPicking(osv.osv):
_inherit = 'stock.picking'
@ -171,21 +198,6 @@ class StockPicking(osv.osv):
return list(set(todo))
class split_in_production_lot(osv.osv_memory):
_inherit = "stock.move.split"
def split(self, cr, uid, ids, move_ids, context=None):
""" Splits move lines into given quantities.
@param move_ids: Stock moves.
@return: List of new moves.
"""
new_moves = super(split_in_production_lot, self).split(cr, uid, ids, move_ids, context=context)
production_obj = self.pool.get('mrp.production')
production_ids = production_obj.search(cr, uid, [('move_lines', 'in', move_ids)])
production_obj.write(cr, uid, production_ids, {'move_lines': [(4, m) for m in new_moves]})
return new_moves
class stock_warehouse(osv.osv):
_inherit = 'stock.warehouse'
_columns = {

View File

@ -49,10 +49,12 @@
!python {model: mrp.product.produce}: |
context.update({'active_id': ref('mrp_production_servicetype_mo1')})
-
!record {model: mrp.product.produce, id: mrp_product_produce_1}:
!record {model: mrp.product.produce, id: mrp_product_produce_1, view: mrp.view_mrp_product_produce_wizard}:
mode: 'consume_produce'
-
!python {model: mrp.product.produce}: |
lines = self.on_change_qty(cr, uid, [ref('mrp_product_produce_1')], 1.0, [], context=context)
self.write(cr, uid, [ref('mrp_product_produce_1')], lines['value'], context=context)
self.do_produce(cr, uid, [ref('mrp_product_produce_1')], context=context)
-
I check production order after produced.
@ -60,67 +62,3 @@
!python {model: mrp.production}: |
order = self.browse(cr, uid, ref("mrp_production_servicetype_mo1"))
assert order.state == 'done', "Production order should be closed."
-
I create Bill of Materials with two service type products.
-
!record {model: mrp.bom, id: mrp_bom_test_2}:
company_id: base.main_company
name: PC Assemble SC234
product_id: product.product_product_3
product_qty: 1.0
type: normal
bom_lines:
- company_id: base.main_company
name: On Site Monitoring
product_id: product.product_product_1
product_qty: 1.0
- company_id: base.main_company
name: On Site Assistance
product_id: product.product_product_2
product_qty: 1.0
-
I make the production order using BoM having two service type products.
-
!record {model: mrp.production, id: mrp_production_servicetype_2}:
product_id: product.product_product_3
product_qty: 1.0
bom_id: mrp_bom_test_2
date_planned: !eval time.strftime('%Y-%m-%d %H:%M:%S')
-
I compute the data of production order.
-
!python {model: mrp.production}: |
self.action_compute(cr, uid, [ref("mrp_production_servicetype_2")], {"lang": "en_US", "tz": False, "search_default_Current": 1,
"active_model": "ir.ui.menu", "active_ids": [ref("mrp.menu_mrp_production_action")],
"active_id": ref("mrp.menu_mrp_production_action"), })
-
I confirm the production order.
-
!workflow {model: mrp.production, action: button_confirm, ref: mrp_production_servicetype_2}
-
Now I start production.
-
!workflow {model: mrp.production, action: button_produce, ref: mrp_production_servicetype_2}
-
I check that production order in production state after start production.
-
!python {model: mrp.production}: |
order = self.browse(cr, uid, ref("mrp_production_servicetype_2"))
#assert order.state == 'in_production', 'Production order should be in production State.'
-
I produce product.
-
!python {model: mrp.product.produce}: |
context.update({'active_id': ref('mrp_production_servicetype_2')})
-
!record {model: mrp.product.produce, id: mrp_product_produce_2}:
mode: 'consume_produce'
-
!python {model: mrp.product.produce}: |
self.do_produce(cr, uid, [ref('mrp_product_produce_2')], context=context)
-
I check production order after produced.
-
!python {model: mrp.production}: |
order = self.browse(cr, uid, ref("mrp_production_servicetype_2"))
#assert order.state == 'done', "Production order should be closed."

View File

@ -154,6 +154,9 @@
mode: 'consume_produce'
-
!python {model: mrp.product.produce}: |
qty = self.browse(cr, uid, ref('mrp_product_produce1')).product_qty
lines = self.on_change_qty(cr, uid, [ref('mrp_product_produce1')], qty, [], context=context)
self.write(cr, uid, [ref('mrp_product_produce1')], lines['value'], context=context)
self.do_produce(cr, uid, [ref('mrp_product_produce1')], context=context)
-
I check production order after produced.

View File

@ -23,6 +23,7 @@ import mrp_product_produce
import mrp_price
import mrp_workcenter_load
import change_production_qty
import stock_move
#import mrp_change_standard_price
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -22,19 +22,56 @@
from openerp.osv import fields, osv
import openerp.addons.decimal_precision as dp
class mrp_product_produce_line(osv.osv_memory):
_name="mrp.product.produce.line"
_description = "Product Produce Consume lines"
_columns = {
'product_id': fields.many2one('product.product', 'Product'),
'product_qty': fields.float('Quantity'),
'lot_id': fields.many2one('stock.production.lot', 'Lot'),
'produce_id': fields.many2one('mrp.product.produce'),
'track_production': fields.related('product_id', 'track_production', type='boolean')
}
class mrp_product_produce(osv.osv_memory):
_name = "mrp.product.produce"
_description = "Product Produce"
_columns = {
'product_id': fields.many2one('product.product', type='many2one'),
'product_qty': fields.float('Select Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
'mode': fields.selection([('consume_produce', 'Consume & Produce'),
('consume', 'Consume Only')], 'Mode', required=True,
help="'Consume only' mode will only consume the products with the quantity selected.\n"
"'Consume & Produce' mode will consume as well as produce the products with the quantity selected "
"and it will finish the production order when total ordered quantities are produced."),
'lot_id': fields.many2one('stock.production.lot', 'Lot'), #Should only be visible when it is consume and produce mode
'consume_lines': fields.one2many('mrp.product.produce.line', 'produce_id', 'Products Consumed'),
'track_production': fields.boolean('Track production'),
}
def on_change_qty(self, cr, uid, ids, product_qty, consume_lines, context=None):
"""
When changing the quantity of products to be produced it will
recalculate the number of raw materials needed according
to the scheduled products and the already consumed/produced products
It will return the consume lines needed for the products to be produced
which the user can still adapt
"""
prod_obj = self.pool.get("mrp.production")
production = prod_obj.browse(cr, uid, context['active_id'], context=context)
consume_lines = []
new_consume_lines = []
if product_qty > 0.0:
consume_lines = prod_obj._calculate_qty(cr, uid, production, product_qty=product_qty, context=context)
for consume in consume_lines:
new_consume_lines.append([0, False, consume])
return {'value': {'consume_lines': new_consume_lines}}
def _get_product_qty(self, cr, uid, context=None):
""" To obtain product quantity
@param self: The object pointer.
@ -54,9 +91,26 @@ class mrp_product_produce(osv.osv_memory):
done += move.product_qty
return (prod.product_qty - done) or prod.product_qty
def _get_product_id(self, cr, uid, context=None):
""" To obtain product id
@return: id
"""
prod=False
if context and context.get("active_id"):
prod = self.pool.get('mrp.production').browse(cr, uid,
context['active_id'], context=context)
return prod and prod.product_id.id or False
def _get_track(self, cr, uid, context=None):
prod = self._get_product_id(cr, uid, context=context)
prod_obj = self.pool.get("product.product")
return prod and prod_obj.browse(cr, uid, prod, context=context).track_production or False
_defaults = {
'product_qty': _get_product_qty,
'mode': lambda *x: 'consume_produce'
'mode': lambda *x: 'consume_produce',
'product_id': _get_product_id,
'track_production': _get_track,
}
def do_produce(self, cr, uid, ids, context=None):
@ -64,7 +118,7 @@ class mrp_product_produce(osv.osv_memory):
assert production_id, "Production Id should be specified in context as a Active ID."
data = self.browse(cr, uid, ids[0], context=context)
self.pool.get('mrp.production').action_produce(cr, uid, production_id,
data.product_qty, data.mode, context=context)
data.product_qty, data.mode, data, context=context)
return {}

View File

@ -12,7 +12,24 @@
<form string="Produce" version="7.0">
<group string="Produce">
<field name="mode"/>
<field name="product_qty" colspan="2"/>
<field name="product_qty" colspan="2" on_change="on_change_qty(product_qty, consume_lines, context)"/>
<field name="product_id" invisible="1"/>
<field name="track_production" invisible="1"/>
<field name="lot_id" domain="[('product_id', '=', product_id)]"
context="{'default_product_id':product_id}"
attrs="{'required': [('track_production', '=', True), ('mode', '=', 'consume_produce')]}"
groups="stock.group_tracking_lot"/>
</group>
<group string="To Consume">
<field name="consume_lines">
<tree string="Consume Lines" editable="top">
<field name="product_id"/>
<field name="product_qty"/>
<field name="lot_id" domain="[('product_id', '=', product_id)]"
context="{'default_product_id':product_id}"
groups="stock.group_tracking_lot"/>
</tree>
</field>
</group>
<footer>
<button name="do_produce" type="object" string="Confirm" class="oe_highlight"/>

View File

@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import fields, osv
from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
class stock_move_consume(osv.osv_memory):
_name = "stock.move.consume"
_description = "Consume Products"
_columns = {
'product_id': fields.many2one('product.product', 'Product', required=True, select=True),
'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
'location_id': fields.many2one('stock.location', 'Location', required=True),
'restrict_lot_id': fields.many2one('stock.production.lot', 'Lot'),
}
#TOFIX: product_uom should not have different category of default UOM of product. Qty should be convert into UOM of original move line before going in consume and scrap
def default_get(self, cr, uid, fields, context=None):
""" Get default values
@param self: The object pointer.
@param cr: A database cursor
@param uid: ID of the user currently logged in
@param fields: List of fields for default value
@param context: A standard dictionary
@return: default values of fields
"""
if context is None:
context = {}
res = super(stock_move_consume, self).default_get(cr, uid, fields, context=context)
move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context)
if 'product_id' in fields:
res.update({'product_id': move.product_id.id})
if 'product_uom' in fields:
res.update({'product_uom': move.product_uom.id})
if 'product_qty' in fields:
res.update({'product_qty': move.product_qty})
if 'location_id' in fields:
res.update({'location_id': move.location_id.id})
return res
def do_move_consume(self, cr, uid, ids, context=None):
""" To move consumed products
@param self: The object pointer.
@param cr: A database cursor
@param uid: ID of the user currently logged in
@param ids: the ID or list of IDs if we want more than one
@param context: A standard dictionary
@return:
"""
if context is None:
context = {}
move_obj = self.pool.get('stock.move')
move_ids = context['active_ids']
for data in self.browse(cr, uid, ids, context=context):
move_obj.action_consume(cr, uid, move_ids,
data.product_qty, data.location_id.id, restrict_lot_id=data.restrict_lot_id.id,
context=context)
return {'type': 'ir.actions.act_window_close'}

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Consume, scrap move -->
<record id="view_stock_move_consume_wizard" model="ir.ui.view">
<field name="name">Consume Move</field>
<field name="model">stock.move.consume</field>
<field name="arch" type="xml">
<form string="Consume Move" version="7.0">
<group string="Consume Products">
<field name="product_id" readonly="1"/>
<label for="product_qty"/>
<div>
<field name="product_qty" class="oe_inline"/>
<field name="product_uom" class="oe_inline" readonly="1" groups="product.group_uom"/>
</div>
<field name="restrict_lot_id" domain="[('product_id','=',product_id)]" groups="stock.group_tracking_lot"
context="{'default_product_id': product_id}"/>
<field name="location_id" groups="stock.group_locations"/>
</group>
<footer>
<button name="do_move_consume" string="Ok" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="move_consume" model="ir.actions.act_window">
<field name="name">Consume Move</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">stock.move.consume</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</openerp>

View File

@ -64,6 +64,11 @@
I finish the production order.
-
!python {model: mrp.product.produce}: |
qty = self.browse(cr, uid, ref('mrp_product_produce0')).product_qty
ctx = context.copy()
ctx['active_id'] = ref("mrp_production_mo0")
lines = self.on_change_qty(cr, uid, [ref('mrp_product_produce0')], qty, [], context=ctx)
self.write(cr, uid, [ref('mrp_product_produce0')], lines['value'], context=context)
self.do_produce(cr, uid, [ref("mrp_product_produce0")], {"active_model": "mrp.production", "active_ids":[ref("mrp_production_mo0")], "active_id": ref("mrp_production_mo0")})
-
I see that stock moves of External Hard Disk including Headset USB are done now.

View File

@ -74,9 +74,7 @@ Dashboard / Reports for Warehouse Management will include:
'stock_data.yml',
'wizard/stock_move_view.xml',
'wizard/stock_change_product_qty_view.xml',
'wizard/stock_inventory_merge_view.xml',
'wizard/stock_location_product_view.xml',
'wizard/stock_inventory_line_split_view.xml',
'wizard/stock_return_picking_view.xml',
'wizard/make_procurement_view.xml',
'wizard/mrp_procurement_view.xml',

View File

@ -287,13 +287,15 @@ class stock_quant(osv.osv):
'''This function reserves quants for the given move (and optionally given link). If the total of quantity reserved is enough, the move's state
is also set to 'assigned'
:param quants: list of tuple(quant browse record or None, qty to reserve). If None is given as first tuple element, the item will be ignored
:param quants: list of tuple(quant browse record or None, qty to reserve). If None is given as first tuple element, the item will be ignored. Negative quants should not be received as argument
:param move: browse record
:param link: browse record (stock.move.operation.link)
'''
toreserve = []
#split quants if needed
for quant, qty in quants:
if qty <= 0.0 or (quant and quant.qty <= 0.0):
raise osv.except_osv(_('Error!'), _('You can not reserve a negative quantity or a negative quant.'))
if not quant:
continue
self._quant_split(cr, uid, quant, qty, context=context)
@ -2011,39 +2013,6 @@ class stock_move(osv.osv):
self.action_done(cr, uid, res, context=context)
return res
def action_consume(self, cr, uid, ids, quantity, location_id=False, restrict_lot_id=False, restrict_partner_id=False, context=None):
""" Consumed product with specific quantity from specific source location. This correspond to a split of the move (or write if the quantity to consume is >= than the quantity of the move) followed by an action_done
@param ids: ids of stock move object to be consumed
@param quantity : specify consume quantity (given in move UoM)
@param location_id : specify source location
@return: Consumed lines
"""
uom_obj = self.pool.get('product.uom')
if context is None:
context = {}
if quantity <= 0:
raise osv.except_osv(_('Warning!'), _('Please provide proper quantity.'))
res = []
for move in self.browse(cr, uid, ids, context=context):
move_qty = move.product_qty
uom_qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, quantity, move.product_uom.id)
if move_qty <= 0:
raise osv.except_osv(_('Error!'), _('Cannot consume a move with negative or zero quantity.'))
quantity_rest = move.product_qty - uom_qty
if quantity_rest > 0:
ctx = context.copy()
if location_id:
ctx['source_location_id'] = location_id
res.append(self.split(cr, uid, move, move_qty - quantity_rest, restrict_lot_id=restrict_lot_id,
restrict_partner_id=restrict_partner_id, context=ctx))
else:
res.append(move.id)
if location_id:
self.write(cr, uid, [move.id], {'location_id': location_id, 'restrict_lot_id': restrict_lot_id,
'restrict_partner_id': restrict_partner_id}, context=context)
self.action_done(cr, uid, res, context=context)
return res
def split(self, cr, uid, move, qty, restrict_lot_id=False, restrict_partner_id=False, context=None):
""" Splits qty from move move into a new move
@ -2092,6 +2061,7 @@ class stock_move(osv.osv):
self.action_confirm(cr, uid, [new_move], context=context)
return new_move
class stock_inventory(osv.osv):
_name = "stock.inventory"
_description = "Inventory"

View File

@ -20,8 +20,6 @@
##############################################################################
import stock_move
import stock_inventory_merge
import stock_inventory_line_split
import stock_location_product
import stock_return_picking
import stock_change_product_qty

View File

@ -1,112 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import fields, osv
class stock_inventory_line_split(osv.osv_memory):
_inherit = "stock.move.split"
_name = "stock.inventory.line.split"
_description = "Split inventory lines"
_columns = {
'line_ids': fields.one2many('stock.inventory.line.split.lines', 'wizard_id', 'Serial Numbers'),
'line_exist_ids': fields.one2many('stock.inventory.line.split.lines', 'wizard_exist_id', 'Serial Numbers'),
}
def default_get(self, cr, uid, fields, context=None):
if context is None:
context = {}
record_id = context and context.get('active_id',False)
res = {}
line = self.pool.get('stock.inventory.line').browse(cr, uid, record_id, context=context)
if 'product_id' in fields:
res.update({'product_id':line.product_id.id})
if 'product_uom' in fields:
res.update({'product_uom': line.product_uom_id.id})
if 'qty' in fields:
res.update({'qty': line.product_qty})
return res
def split(self, cr, uid, ids, line_ids, context=None):
""" To split stock inventory lines according to serial numbers.
:param line_ids: the ID or list of IDs of inventory lines we want to split
"""
if context is None:
context = {}
assert context.get('active_model') == 'stock.inventory.line',\
'Incorrect use of the inventory line split wizard.'
prodlot_obj = self.pool.get('stock.production.lot')
ir_sequence_obj = self.pool.get('ir.sequence')
line_obj = self.pool.get('stock.inventory.line')
new_line = []
for data in self.browse(cr, uid, ids, context=context):
for inv_line in line_obj.browse(cr, uid, line_ids, context=context):
line_qty = inv_line.product_qty
quantity_rest = inv_line.product_qty
new_line = []
if data.use_exist:
lines = [l for l in data.line_exist_ids if l]
else:
lines = [l for l in data.line_ids if l]
for line in lines:
quantity = line.quantity
if quantity <= 0 or line_qty == 0:
continue
quantity_rest -= quantity
if quantity_rest < 0:
quantity_rest = quantity
break
default_val = {
'product_qty': quantity,
}
if quantity_rest > 0:
current_line = line_obj.copy(cr, uid, inv_line.id, default_val)
new_line.append(current_line)
if quantity_rest == 0:
current_line = inv_line.id
lot_id = False
if data.use_exist:
lot_id = line.lot_id.id
if not lot_id:
lot_id = prodlot_obj.create(cr, uid, {
'name': line.name,
'product_id': inv_line.product_id.id},
context=context)
line_obj.write(cr, uid, [current_line], {'prod_lot_id': lot_id})
prodlot = prodlot_obj.browse(cr, uid, lot_id)
update_val = {}
if quantity_rest > 0:
update_val['product_qty'] = quantity_rest
line_obj.write(cr, uid, [inv_line.id], update_val)
return new_line
class stock_inventory_split_lines(osv.osv_memory):
_inherit = "stock.move.split.lines"
_name = "stock.inventory.line.split.lines"
_description = "Inventory Split lines"
_columns = {
'wizard_id': fields.many2one('stock.inventory.line.split', 'Parent Wizard'),
'wizard_exist_id': fields.many2one('stock.inventory.line.split', 'Parent Wizard'),
}

View File

@ -1,61 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_split_in_lots_inherit" model="ir.ui.view">
<field name="name">Split Inventory Line</field>
<field name="model">stock.inventory.line.split</field>
<field name="arch" type="xml">
<form string="Split in Serial Numbers" version="7.0">
<group>
<field name="product_id" colspan="4" readonly="1"/>
<label for="qty"/>
<div>
<field name="qty" readonly="1"/>
<field name="product_uom" readonly="1"/>
</div>
<field name="use_exist"/>
</group>
<field name="line_ids" attrs="{'invisible':[('use_exist','=',True)]}">
<tree string="Lots Number" editable="bottom">
<field name="name" string="Lots"/>
<field name="quantity" />
</tree>
<form string="Lots Number" version="7.0">
<group>
<field name="name" string="Lots"/>
<field name="quantity" />
</group>
</form>
</field>
<field name="line_exist_ids" attrs="{'invisible':[('use_exist','!=',True)]}">
<tree string="Lots Number" editable="bottom">
<field name="lot_id" string="Lots" domain="[('product_id','=',parent.product_id)]"/>
<field name="quantity" />
</tree>
<form string="Lots Number" version="7.0">
<group>
<field name="lot_id" string="Lots" domain="[('product_id','=',parent.product_id)]"/>
<field name="quantity" />
</group>
</form>
</field>
<footer>
<button name="split_lot" string="Ok" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_view_stock_inventory_line_split" model="ir.actions.act_window">
<field name="name">Split inventory lines</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">stock.inventory.line.split</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</openerp>

View File

@ -1,91 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import fields, osv
from openerp.tools.translate import _
class stock_inventory_merge(osv.osv_memory):
_name = "stock.inventory.merge"
_description = "Merge Inventory"
def fields_view_get(self, cr, uid, view_id=None, view_type='form',
context=None, toolbar=False, submenu=False):
"""
Changes the view dynamically
@param self: The object pointer.
@param cr: A database cursor
@param uid: ID of the user currently logged in
@param context: A standard dictionary
@return: New arch of view.
"""
if context is None:
context={}
res = super(stock_inventory_merge, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False)
if context.get('active_model','') == 'stock.inventory' and len(context['active_ids']) < 2:
raise osv.except_osv(_('Warning!'),
_('Please select multiple physical inventories to merge in the list view.'))
return res
def do_merge(self, cr, uid, ids, context=None):
""" To merge selected Inventories.
@param self: The object pointer.
@param cr: A database cursor
@param uid: ID of the user currently logged in
@param ids: List of IDs selected
@param context: A standard dictionary
@return:
"""
invent_obj = self.pool.get('stock.inventory')
invent_line_obj = self.pool.get('stock.inventory.line')
invent_lines = {}
if context is None:
context = {}
for inventory in invent_obj.browse(cr, uid, context['active_ids'], context=context):
if inventory.state == "done":
raise osv.except_osv(_('Warning!'),
_('Merging is only allowed on draft inventories.'))
for line in inventory.inventory_line_id:
key = (line.location_id.id, line.product_id.id, line.product_uom_id.id)
if key in invent_lines:
invent_lines[key] += line.product_qty
else:
invent_lines[key] = line.product_qty
new_invent = invent_obj.create(cr, uid, {
'name': 'Merged inventory'
}, context=context)
for key, quantity in invent_lines.items():
invent_line_obj.create(cr, uid, {
'inventory_id': new_invent,
'location_id': key[0],
'product_id': key[1],
'product_uom_id': key[2],
'product_qty': quantity,
})
return {'type': 'ir.actions.act_window_close'}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,30 +0,0 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="name_form" model="ir.ui.view">
<field name="name">stock.inventory.merge.form</field>
<field name="model">stock.inventory.merge</field>
<field name="arch" type="xml">
<form string="Merge Inventories" version="7.0">
<separator string="Merge Inventories" />
<label string="Do you want to merge theses inventories?"/>
<footer>
<button name="do_merge" string="Yes" type="object" icon="gtk-apply" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<act_window name="Merge inventories"
res_model="stock.inventory.merge"
src_model="stock.inventory"
view_mode="form"
multi="True"
target="new"
key2="client_action_multi"
id="action_view_stock_merge_inventories"/>
</data>
</openerp>

View File

@ -23,9 +23,11 @@ from openerp.osv import fields, osv
from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
class stock_move_consume(osv.osv_memory):
_name = "stock.move.consume"
_description = "Consume Products"
class stock_move_scrap(osv.osv_memory):
_name = "stock.move.scrap"
_description = "Scrap Products"
_columns = {
'product_id': fields.many2one('product.product', 'Product', required=True, select=True),
@ -35,57 +37,6 @@ class stock_move_consume(osv.osv_memory):
'restrict_lot_id': fields.many2one('stock.production.lot', 'Lot'),
}
#TOFIX: product_uom should not have differemt category of default UOM of product. Qty should be convert into UOM of original move line before going in consume and scrap
def default_get(self, cr, uid, fields, context=None):
""" Get default values
@param self: The object pointer.
@param cr: A database cursor
@param uid: ID of the user currently logged in
@param fields: List of fields for default value
@param context: A standard dictionary
@return: default values of fields
"""
if context is None:
context = {}
res = super(stock_move_consume, self).default_get(cr, uid, fields, context=context)
move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context)
if 'product_id' in fields:
res.update({'product_id': move.product_id.id})
if 'product_uom' in fields:
res.update({'product_uom': move.product_uom.id})
if 'product_qty' in fields:
res.update({'product_qty': move.product_qty})
if 'location_id' in fields:
res.update({'location_id': move.location_id.id})
return res
def do_move_consume(self, cr, uid, ids, context=None):
""" To move consumed products
@param self: The object pointer.
@param cr: A database cursor
@param uid: ID of the user currently logged in
@param ids: the ID or list of IDs if we want more than one
@param context: A standard dictionary
@return:
"""
if context is None:
context = {}
move_obj = self.pool.get('stock.move')
move_ids = context['active_ids']
for data in self.browse(cr, uid, ids, context=context):
move_obj.action_consume(cr, uid, move_ids,
data.product_qty, data.location_id.id, restrict_lot_id=data.restrict_lot_id and data.restrict_lot_id.id or False,
context=context)
return {'type': 'ir.actions.act_window_close'}
class stock_move_scrap(osv.osv_memory):
_name = "stock.move.scrap"
_description = "Scrap Products"
_inherit = "stock.move.consume"
_defaults = {
'location_id': lambda *x: False
}
@ -101,8 +52,9 @@ class stock_move_scrap(osv.osv_memory):
"""
if context is None:
context = {}
res = super(stock_move_consume, self).default_get(cr, uid, fields, context=context)
res = super(stock_move_scrap, self).default_get(cr, uid, fields, context=context)
move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context)
location_obj = self.pool.get('stock.location')
scrap_location_id = location_obj.search(cr, uid, [('scrap_location','=',True)])
@ -115,7 +67,6 @@ class stock_move_scrap(osv.osv_memory):
res.update({'location_id': scrap_location_id[0]})
else:
res.update({'location_id': False})
return res
def move_scrap(self, cr, uid, ids, context=None):
@ -139,137 +90,4 @@ class stock_move_scrap(osv.osv_memory):
class split_in_production_lot(osv.osv_memory):
_name = "stock.move.split"
_description = "Split in Serial Numbers"
def default_get(self, cr, uid, fields, context=None):
if context is None:
context = {}
res = super(split_in_production_lot, self).default_get(cr, uid, fields, context=context)
if context.get('active_id'):
move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context)
if 'product_id' in fields:
res.update({'product_id': move.product_id.id})
if 'product_uom' in fields:
res.update({'product_uom': move.product_uom.id})
if 'qty' in fields:
res.update({'qty': move.product_qty})
if 'use_exist' in fields:
res.update({'use_exist': (move.picking_id and move.picking_id.type=='out' and True) or False})
if 'location_id' in fields:
res.update({'location_id': move.location_id.id})
return res
_columns = {
'qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure')),
'product_id': fields.many2one('product.product', 'Product', required=True, select=True),
'product_uom': fields.many2one('product.uom', 'Unit of Measure'),
'line_ids': fields.one2many('stock.move.split.lines', 'wizard_id', 'Serial Numbers'),
'line_exist_ids': fields.one2many('stock.move.split.lines', 'wizard_exist_id', 'Serial Numbers'),
'use_exist' : fields.boolean('Existing Serial Numbers',
help="Check this option to select existing serial numbers in the list below, otherwise you should enter new ones line by line."),
'location_id': fields.many2one('stock.location', 'Source Location')
}
def split_lot(self, cr, uid, ids, context=None):
""" To split a lot"""
if context is None:
context = {}
res = self.split(cr, uid, ids, context.get('active_ids'), context=context)
return {'type': 'ir.actions.act_window_close'}
def split(self, cr, uid, ids, move_ids, context=None):
""" To split stock moves into serial numbers
:param move_ids: the ID or list of IDs of stock move we want to split
"""
if context is None:
context = {}
assert context.get('active_model') == 'stock.move',\
'Incorrect use of the stock move split wizard'
inventory_id = context.get('inventory_id', False)
prodlot_obj = self.pool.get('stock.production.lot')
inventory_obj = self.pool.get('stock.inventory')
move_obj = self.pool.get('stock.move')
new_move = []
for data in self.browse(cr, uid, ids, context=context):
for move in move_obj.browse(cr, uid, move_ids, context=context):
move_qty = move.product_qty
quantity_rest = move.product_qty
uos_qty_rest = move.product_uos_qty
new_move = []
if data.use_exist:
lines = [l for l in data.line_exist_ids if l]
else:
lines = [l for l in data.line_ids if l]
total_move_qty = 0.0
for line in lines:
quantity = line.quantity
total_move_qty += quantity
if total_move_qty > move_qty:
raise osv.except_osv(_('Processing Error!'), _('Serial number quantity %d of %s is larger than available quantity (%d)!') \
% (total_move_qty, move.product_id.name, move_qty))
if quantity <= 0 or move_qty == 0:
continue
quantity_rest -= quantity
uos_qty = quantity / move_qty * move.product_uos_qty
uos_qty_rest = quantity_rest / move_qty * move.product_uos_qty
if quantity_rest < 0:
quantity_rest = quantity
self.pool.get('stock.move').log(cr, uid, move.id, _('Unable to assign all lots to this move!'))
return False
default_val = {
'product_qty': quantity,
'product_uos_qty': uos_qty,
'state': move.state
}
if quantity_rest > 0:
current_move = move_obj.copy(cr, uid, move.id, default_val, context=context)
if inventory_id and current_move:
inventory_obj.write(cr, uid, inventory_id, {'move_ids': [(4, current_move)]}, context=context)
new_move.append(current_move)
if quantity_rest == 0:
current_move = move.id
lot_id = False
if data.use_exist:
lot_id = line.lot_id.id
if not lot_id:
lot_id = prodlot_obj.create(cr, uid, {
'name': line.name,
'product_id': move.product_id.id},
context=context)
move_obj.write(cr, uid, [current_move], {'lot_id': lot_id, 'state':move.state})
update_val = {}
if quantity_rest > 0:
update_val['product_qty'] = quantity_rest
update_val['product_uos_qty'] = uos_qty_rest
update_val['state'] = move.state
move_obj.write(cr, uid, [move.id], update_val)
return new_move
class stock_move_split_lines_exist(osv.osv_memory):
_name = "stock.move.split.lines"
_description = "Stock move Split lines"
_columns = {
'name': fields.char('Serial Number', size=64),
'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure')),
'wizard_id': fields.many2one('stock.move.split', 'Parent Wizard'),
'wizard_exist_id': fields.many2one('stock.move.split', 'Parent Wizard (for existing lines)'),
'lot_id': fields.many2one('stock.production.lot', 'Serial Number'),
}
_defaults = {
'quantity': 1.0,
}
def onchange_lot_id(self, cr, uid, ids, lot_id=False, product_qty=False,
loc_id=False, product_id=False, uom_id=False,context=None):
return self.pool.get('stock.move').onchange_lot_id(cr, uid, [], lot_id, product_qty,
loc_id, product_id, uom_id, context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,41 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Consume, scrap move -->
<record id="view_stock_move_consume_wizard" model="ir.ui.view">
<field name="name">Consume Move</field>
<field name="model">stock.move.consume</field>
<field name="arch" type="xml">
<form string="Consume Move" version="7.0">
<group string="Consume Products">
<field name="product_id" readonly="1"/>
<label for="product_qty"/>
<div>
<field name="product_qty" class="oe_inline"/>
<field name="product_uom" class="oe_inline" readonly="1" groups="product.group_uom"/>
</div>
<field name="restrict_lot_id" domain="[('product_id','=',product_id)]" groups="stock.group_tracking_lot"
context="{'default_product_id': product_id}"/>
<field name="location_id" groups="stock.group_locations"/>
</group>
<footer>
<button name="do_move_consume" string="Ok" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="move_consume" model="ir.actions.act_window">
<field name="name">Consume Move</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">stock.move.consume</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<!-- Scrap move -->
<record id="view_stock_move_scrap_wizard" model="ir.ui.view">
<field name="name">Scrap Move</field>
@ -74,62 +40,5 @@
<field name="target">new</field>
</record>
<record id="view_split_in_lots" model="ir.ui.view">
<field name="name">Split in Serial Numbers</field>
<field name="model">stock.move.split</field>
<field name="arch" type="xml">
<form string="Split in Serial Numbers" version="7.0">
<group>
<field name="product_id" readonly="1"/>
<label for="qty"/>
<div>
<field name="qty" readonly="1" class="oe_inline"/>
<field name="product_uom" readonly="1" class="oe_inline"/>
</div>
<field name="location_id" invisible="1"/>
<field name="use_exist"/>
</group>
<field name="line_ids" attrs="{'invisible':[('use_exist','=',True)]}">
<tree string="Serial Numbers" editable="bottom">
<field name="name"/>
<field name="quantity" />
</tree>
<form string="Serial Number" version="7.0">
<group>
<field name="name"/>
<field name="quantity" />
</group>
</form>
</field>
<field name="line_exist_ids" attrs="{'invisible':[('use_exist','!=',True)]}">
<tree string="Serial Numbers" editable="bottom">
<field name="lot_id" string="Serial Number" quick_create="false" domain="[('product_id','=',parent.product_id)]" on_change="onchange_lot_id(lot_id, quantity, parent.location_id, parent.product_id, parent.product_uom, context)" context="{'product_id': parent.product_id}"/>
<field name="quantity" on_change="onchange_lot_id(lot_id, quantity, parent.location_id, parent.product_id, parent.product_uom,context)" />
</tree>
<form string="Serial Number" version="7.0">
<group>
<field name="lot_id" domain="[('product_id','=',parent.product_id)]" on_change="onchange_lot_id(lot_id, quantity, parent.location_id, parent.product_id, parent.product_uom, context)"/>
<field name="quantity" on_change="onchange_lot_id(lot_id, quantity, parent.location_id, parent.product_id, parent.product_uom, context)" />
</group>
</form>
</field>
<footer>
<button name="split_lot" string="Split" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="track_line" model="ir.actions.act_window">
<field name="name">Split in Serial Numbers</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">stock.move.split</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</openerp>