[MERGE] merged with parent branch lp:~openerp-dev/openobject-addons/trunk-wmsimplement-seduce-fp
bzr revid: bde@tinyerp.com-20130827085750-gvlr9t6kqq2eoetd
This commit is contained in:
@@ -123,10 +123,9 @@
|
||||
-
|
||||
Reception is ready for process so now done the reception.
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001")).picking_ids
|
||||
partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
!python {model: stock.picking}: |
|
||||
picking_id = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001")).picking_ids[0]
|
||||
picking_id.do_partial(context=context)
|
||||
-
|
||||
I check the Stock Interim account (Received) is credited successfully.
|
||||
-
|
||||
@@ -189,7 +188,6 @@
|
||||
-
|
||||
!record {model: stock.picking, id: stock_picking_out001}:
|
||||
partner_id: base.res_partner_13
|
||||
invoice_state: 2binvoiced
|
||||
move_lines:
|
||||
- company_id: base.main_company
|
||||
location_id: stock.stock_location_stock
|
||||
@@ -198,29 +196,28 @@
|
||||
product_uom: product.product_uom_unit
|
||||
location_dest_id: stock.stock_location_customers
|
||||
move_type: direct
|
||||
type: out
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
I need to check the availability of the product, So I make my picking order for processing later.
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
self.draft_force_assign(cr, uid, [ref("stock_picking_out001")], {"lang": "en_US", "search_default_available":
|
||||
1, "tz": False, "active_model": "ir.ui.menu", "contact_display": "partner",
|
||||
"active_ids": [ref("stock.menu_action_picking_tree")], "active_id": ref("stock.menu_action_picking_tree"),
|
||||
})
|
||||
self.action_confirm(cr, uid, [ref('stock_picking_out001')], context=context)
|
||||
|
||||
-
|
||||
I check the product availability, Product is available in the stock and ready to be sent.
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
self.action_assign(cr, uid, [ref("stock_picking_out001")], {"lang": "en_US", "search_default_available":
|
||||
1, "tz": False, "active_model": "ir.ui.menu", "contact_display": "partner",
|
||||
"active_ids": [ref("stock.menu_action_picking_tree")], "active_id": ref("stock.menu_action_picking_tree"),
|
||||
})
|
||||
picking = self.browse(cr, uid, ref("stock_picking_out001"))
|
||||
assert picking.state == "confirmed", "Picking should be confirmed."
|
||||
for move_line in picking.move_lines:
|
||||
assert move_line.state == "confirmed", "Move should be confirmed."
|
||||
|
||||
-
|
||||
I process the delivery.
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model':'stock.picking','active_ids':[ref('stock_picking_out001')]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
!python {model: stock.picking}: |
|
||||
picking = self.pool.get('stock.picking').browse(cr, uid, ref("stock_picking_out001"))
|
||||
picking.do_partial(context=context)
|
||||
-
|
||||
I check Stock Interim account (Delivery) is debited successfully.
|
||||
-
|
||||
|
||||
@@ -131,10 +131,9 @@
|
||||
-
|
||||
Reception is ready for process so now done the reception.
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001_fifo")).picking_ids
|
||||
partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
!python {model: stock.picking}: |
|
||||
picking_id = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001_fifo")).picking_ids[0]
|
||||
picking_id.do_partial(context=context)
|
||||
-
|
||||
I check the Stock Interim account (Received) is credit successfully.
|
||||
-
|
||||
@@ -192,7 +191,6 @@
|
||||
-
|
||||
!record {model: stock.picking, id: stock_picking_out001_fifo}:
|
||||
partner_id: base.res_partner_13
|
||||
invoice_state: 2binvoiced
|
||||
move_lines:
|
||||
- company_id: base.main_company
|
||||
location_id: stock.stock_location_stock
|
||||
@@ -200,29 +198,26 @@
|
||||
product_qty: 1.0
|
||||
location_dest_id: stock.stock_location_customers
|
||||
move_type: direct
|
||||
type: out
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
I need to check the availability of the product, So I make my picking order for processing later.
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
self.draft_force_assign(cr, uid, [ref("stock_picking_out001_fifo")], {"lang": "en_US", "search_default_available":
|
||||
1, "tz": False, "active_model": "ir.ui.menu", "contact_display": "partner",
|
||||
"active_ids": [ref("stock.menu_action_picking_tree")], "active_id": ref("stock.menu_action_picking_tree"),
|
||||
})
|
||||
self.action_confirm(cr, uid, [ref('stock_picking_out001')], context=context)
|
||||
-
|
||||
I check the product availability, Product is available in the stock and ready to be sent.
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
self.action_assign(cr, uid, [ref("stock_picking_out001_fifo")], {"lang": "en_US", "search_default_available":
|
||||
1, "tz": False, "active_model": "ir.ui.menu", "contact_display": "partner",
|
||||
"active_ids": [ref("stock.menu_action_picking_tree")], "active_id": ref("stock.menu_action_picking_tree"),
|
||||
})
|
||||
picking = self.browse(cr, uid, ref("stock_picking_out001"))
|
||||
assert picking.state == "confirmed", "Picking should be confirmed."
|
||||
for move_line in picking.move_lines:
|
||||
assert move_line.state == "confirmed", "Move should be confirmed."
|
||||
-
|
||||
I process the delivery.
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model':'stock.picking','active_ids':[ref('stock_picking_out001_fifo')]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
!python {model: stock.picking}: |
|
||||
picking = self.pool.get('stock.picking').browse(cr, uid, ref("stock_picking_out001_fifo"))
|
||||
picking.do_partial(context=context)
|
||||
-
|
||||
I check Stock Interim account (Delivery) is debited successfully.
|
||||
-
|
||||
|
||||
@@ -81,7 +81,6 @@ Dashboard / Reports for Purchase Management will include:
|
||||
'test/ui/duplicate_order.yml',
|
||||
'test/ui/delete_order.yml',
|
||||
'test/average_price.yml',
|
||||
'test/lifo_price.yml',
|
||||
],
|
||||
'demo': [
|
||||
'purchase_order_demo.yml',
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
-
|
||||
Now I create an invoice for order on reception.
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
pick_ids = self.search(cr, uid, [('purchase_id','=',ref('purchase_order_2'))])
|
||||
self.action_invoice_create(cr, uid, pick_ids, ref('account.expenses_journal'))
|
||||
!python {model: purchase.order}: |
|
||||
pick_ids = [self.browse(cr, uid, ref("purchase_order_2")).id]
|
||||
self.action_invoice_create(cr, uid, pick_ids)
|
||||
-
|
||||
I check that the invoice of order.
|
||||
-
|
||||
|
||||
@@ -45,10 +45,9 @@
|
||||
-
|
||||
Reception is ready for process so now done the reception.
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_1")).picking_ids
|
||||
partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
!python {model: stock.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_1")).picking_ids[0]
|
||||
pick_ids.do_partial(context=context)
|
||||
-
|
||||
I check that purchase order is shipped.
|
||||
-
|
||||
|
||||
+1
-1
@@ -658,7 +658,7 @@ class sale_order(osv.osv):
|
||||
for order in self.browse(cr, uid, ids, context=context):
|
||||
proc_ids = []
|
||||
group_id = self.pool.get("procurement.group").create(cr, uid, {
|
||||
'name': order.name, 'partner_id': order.partner_shipping_id.id, 'move_type': order.picking_policy
|
||||
'name': order.name, 'partner_id': order.partner_shipping_id.id
|
||||
}, context=context)
|
||||
order.write({'procurement_group_id': group_id}, context=context)
|
||||
for line in order.order_line:
|
||||
|
||||
@@ -123,7 +123,6 @@ class stock_picking(osv.osv):
|
||||
'company_id': sale.company_id.id,
|
||||
'currency_id': sale.pricelist_id.currency_id.id,
|
||||
'journal_id': journal_id,
|
||||
'comment': sale.note
|
||||
}, context=context)
|
||||
invoices[key] = invoice_id
|
||||
|
||||
|
||||
@@ -31,20 +31,14 @@
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
order = self.browse(cr, uid, ref("sale.sale_order_6"))
|
||||
for order_line in order.order_line:
|
||||
procurement = order_line.procurement_id
|
||||
sale_order_date = self.date_to_datetime(cr, uid, order.date_order, context)
|
||||
date_planned = datetime.strptime(sale_order_date, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0)
|
||||
procurement = order_line.procurement_ids[0]
|
||||
date_planned = datetime.strptime(order.date_order, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0)
|
||||
date_planned = (date_planned - timedelta(days=order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
assert procurement.date_planned == date_planned, "Scheduled date is not correspond."
|
||||
assert procurement.product_id.id == order_line.product_id.id, "Product is not correspond."
|
||||
assert procurement.product_qty == order_line.product_uom_qty, "Qty is not correspond."
|
||||
assert procurement.product_uom.id == order_line.product_uom.id, "UOM is not correspond."
|
||||
assert procurement.procure_method == order_line.type, "Procurement method is not correspond."
|
||||
-
|
||||
I run the scheduler.
|
||||
-
|
||||
!python {model: procurement.order}: |
|
||||
self.run_scheduler(cr, uid)
|
||||
assert procurement.move_ids[0].procure_method == order_line.type, "Procurement method is not correspond."
|
||||
-
|
||||
I check the details of delivery order after confirmed quotation.
|
||||
-
|
||||
@@ -57,7 +51,7 @@
|
||||
for picking in sale_order.picking_ids:
|
||||
assert picking.state == "auto" or "confirmed", "Delivery order should be in 'Waitting Availability' state."
|
||||
assert picking.origin == sale_order.name,"Origin of Delivery order is not correspond with sequence number of sale order."
|
||||
assert picking.type == 'out',"Shipment should be Outgoing."
|
||||
assert picking.picking_type_id == self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'picking_type_out', context=context),"Shipment should be Outgoing."
|
||||
assert picking.move_type == sale_order.picking_policy,"Delivery Method is not corresponding with delivery method of sale order."
|
||||
assert picking.partner_id.id == sale_order.partner_shipping_id.id,"Shipping Address is not correspond with sale order."
|
||||
assert picking.note == sale_order.note,"Note is not correspond with sale order."
|
||||
@@ -66,9 +60,8 @@
|
||||
location_id = sale_order.warehouse_id.lot_stock_id.id
|
||||
output_id = sale_order.warehouse_id.lot_output_id.id
|
||||
for move in picking.move_lines:
|
||||
order_line = move.sale_line_id
|
||||
sale_order_date = self.date_to_datetime(cr, uid, sale_order.date_order, context)
|
||||
date_planned = datetime.strptime(sale_order_date, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0)
|
||||
order_line = move.procurement_id.sale_line_id
|
||||
date_planned = datetime.strptime(sale_order.date_order, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=order_line.delay or 0.0)
|
||||
date_planned = (date_planned - timedelta(days=sale_order.company_id.security_lead)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
assert datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT) == datetime.strptime(date_planned, DEFAULT_SERVER_DATETIME_FORMAT), "Excepted Date is not correspond with Planned Date."
|
||||
assert move.product_id.id == order_line.product_id.id,"Product is not correspond."
|
||||
@@ -84,13 +77,17 @@
|
||||
-
|
||||
Now, I dispatch delivery order.
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
!python {model: stock.picking}: |
|
||||
order = self.pool.get('sale.order').browse(cr, uid, ref("sale.sale_order_6"))
|
||||
for pick in order.picking_ids:
|
||||
data = pick.force_assign()
|
||||
if data == True:
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [pick.id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
pick.do_partial(context=context)
|
||||
-
|
||||
I run the scheduler.
|
||||
-
|
||||
!python {model: procurement.order}: |
|
||||
self.run_scheduler(cr, uid)
|
||||
-
|
||||
I check sale order to verify shipment.
|
||||
-
|
||||
@@ -123,7 +120,7 @@
|
||||
assert invoice.reference == order.client_order_ref or order.name,"Reference is not correspond."
|
||||
assert invoice.partner_id.id == order.partner_invoice_id.id,"Customer does not correspond."
|
||||
assert invoice.currency_id.id == order.pricelist_id.currency_id.id, "Currency is not correspond."
|
||||
assert invoice.comment == (order.note or ''),"Note is not correspond."
|
||||
assert (invoice.comment or '') == (order.note or ''),"Note is not correspond."
|
||||
assert invoice.journal_id.id in journal_ids,"Sales Journal is not link on Invoice."
|
||||
assert invoice.payment_term.id == order.payment_term.id, "Payment term is not correspond."
|
||||
for so_line in order.order_line:
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
-
|
||||
In order to test the Prepaid Order Policy, I create a product
|
||||
-
|
||||
!record {model: product.product, id: product_prepaid1}:
|
||||
name: 'OpenERP Documentation Book'
|
||||
list_price: 60.60
|
||||
-
|
||||
Now i create a sale order that uses my new product
|
||||
-
|
||||
!record {model: sale.order, id: sale_order_prepaid1}:
|
||||
partner_id: base.res_partner_2
|
||||
order_policy: prepaid
|
||||
order_line:
|
||||
- product_id: sale_stock.product_prepaid1
|
||||
product_uom_qty: 10
|
||||
-
|
||||
Now I confirm the Quotation with "Pay before delivery" policy.
|
||||
-
|
||||
!workflow {model: sale.order, action: order_confirm, ref: sale.sale_order_4}
|
||||
!workflow {model: sale.order, action: order_confirm, ref: sale_order_prepaid1}
|
||||
-
|
||||
I check that delivery order should not created before invoice is paid.
|
||||
-
|
||||
!python {model: sale.order}: |
|
||||
sale_order = self.browse(cr, uid, ref("sale.sale_order_4"))
|
||||
sale_order = self.browse(cr, uid, ref("sale_order_prepaid1"))
|
||||
assert len(sale_order.picking_ids) == False, "Delivery order should not created before invoice."
|
||||
assert sale_order.invoice_ids, "Invoice should be created."
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_order_line_tree_inherit" model="ir.ui.view">
|
||||
<field name="name">sale.order.line.tree.sale.stock.location</field>
|
||||
<field name="inherit_id" ref="sale.view_order_line_tree"/>
|
||||
@@ -29,7 +30,10 @@
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//tree/field[@name='price_unit']" position="before">
|
||||
<xpath expr="//field[@name='order_line']/form/group/group/field[@name='price_unit']" position="before">
|
||||
<field name="route_id"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='order_line']/tree/field[@name='price_unit']" position="before">
|
||||
<field name="route_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
+200
-52
@@ -201,7 +201,6 @@ class stock_quant(osv.osv):
|
||||
else:
|
||||
self._quant_split(cr, uid, quant, qty, context=context)
|
||||
# FP Note: improve this using preferred locations
|
||||
location_to = move.location_dest_id
|
||||
location_to = self.check_preferred_location(cr, uid, move, context=context)
|
||||
self.write(cr, uid, [quant.id], {
|
||||
'location_id': location_to.id,
|
||||
@@ -239,7 +238,7 @@ class stock_quant(osv.osv):
|
||||
# Create a negative quant in the source location if it's an internal location
|
||||
# Reconcile a positive quant with a negative is possible
|
||||
#
|
||||
def _quant_create(self, cr, uid, qty, move, context=None):
|
||||
def _quant_create(self, cr, uid, qty, move, lot_id = False, context=None):
|
||||
# FP Note: TODO: compute the right price according to the move, with currency convert
|
||||
# QTY is normally already converted to main product's UoM
|
||||
price_unit = move.price_unit
|
||||
@@ -251,6 +250,7 @@ class stock_quant(osv.osv):
|
||||
'history_ids': [(4, move.id)],
|
||||
'in_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'company_id': move.company_id.id,
|
||||
'lot_id': lot_id,
|
||||
}
|
||||
|
||||
negative_quant_id = False
|
||||
@@ -318,8 +318,22 @@ class stock_quant(osv.osv):
|
||||
def _price_update(self, cr, uid, quant, newprice, context=None):
|
||||
self.write(cr, uid, [quant.id], {'cost': newprice}, context=context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
#We want to trigger the move with nothing on reserved_quant_ids for the store of the remaining quantity
|
||||
if 'reservation_id' in vals:
|
||||
reservation_ids = self.browse(cr, uid, ids, context=context)
|
||||
moves_to_warn = set()
|
||||
for reser in reservation_ids:
|
||||
if reser.reservation_id:
|
||||
moves_to_warn.add(reser.reservation_id.id)
|
||||
self.pool.get('stock.move').write(cr, uid, list(moves_to_warn), {'reserved_quant_ids': []}, context=context)
|
||||
return super(stock_quant, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
def quants_unreserve(self, cr, uid, move, context=None):
|
||||
cr.execute('update stock_quant set reservation_id=NULL where reservation_id=%s', (move.id,))
|
||||
#cr.execute('update stock_quant set reservation_id=NULL where reservation_id=%s', (move.id,))
|
||||
#need write for related store of remaining qty
|
||||
related_quants = self.search(cr, uid, [('reservation_id', '=', move.id)], context=context)
|
||||
self.write(cr, uid, related_quants, {'reservation_id': False}, context=context)
|
||||
return True
|
||||
|
||||
#
|
||||
@@ -610,16 +624,18 @@ class stock_picking(osv.osv):
|
||||
backorder_moves = picking.move_lines
|
||||
backorder_move_ids = [x.id for x in backorder_moves if x.state not in ('done','cancel')]
|
||||
|
||||
backorder_id = self.copy(cr, uid, picking.id, {
|
||||
'name': '/',
|
||||
'move_lines': [],
|
||||
'pack_operation_ids': [],
|
||||
'backorder_id': picking.id,
|
||||
})
|
||||
back_order_name = self.browse(cr, uid, backorder_id, context=context).name
|
||||
self.message_post(cr, uid, picking.id, body=_("Back order <em>%s</em> <b>created</b>.") % (back_order_name), context=context)
|
||||
self.pool.get('stock.move').write(cr, uid, backorder_move_ids, {'picking_id': backorder_id}, context=context)
|
||||
return backorder_id
|
||||
if backorder_move_ids:
|
||||
backorder_id = self.copy(cr, uid, picking.id, {
|
||||
'name': '/',
|
||||
'move_lines': [],
|
||||
'pack_operation_ids': [],
|
||||
'backorder_id': picking.id,
|
||||
})
|
||||
back_order_name = self.browse(cr, uid, backorder_id, context=context).name
|
||||
self.message_post(cr, uid, picking.id, body=_("Back order <em>%s</em> <b>created</b>.") % (back_order_name), context=context)
|
||||
self.pool.get('stock.move').write(cr, uid, backorder_move_ids, {'picking_id': backorder_id}, context=context)
|
||||
return backorder_id
|
||||
return False
|
||||
|
||||
def do_prepare_partial(self, cr, uid, picking_ids, context=None):
|
||||
context = context or {}
|
||||
@@ -649,7 +665,7 @@ class stock_picking(osv.osv):
|
||||
'cost': move.product_id.standard_price,
|
||||
}, context=context)
|
||||
|
||||
def _do_partial_product_move(self, cr, uid, picking, product, qty, quant=False, context=None):
|
||||
def _do_partial_product_move(self, cr, uid, picking, product, qty, pack_id = False, lot_id = False, quant=False, context=None):
|
||||
moves = []
|
||||
stock_move_obj = self.pool.get('stock.move')
|
||||
for move in picking.move_lines:
|
||||
@@ -684,38 +700,132 @@ class stock_picking(osv.osv):
|
||||
moves.append(move)
|
||||
return moves
|
||||
|
||||
def do_rereserve(self, cr, uid, picking_ids, context=None):
|
||||
'''
|
||||
Needed for parameter create
|
||||
'''
|
||||
self.rereserve(cr, uid, picking_ids, context=context)
|
||||
|
||||
|
||||
def rereserve(self, cr, uid, picking_ids, create=False, context=None):
|
||||
"""
|
||||
This will unreserve all products and reserve the quants from the operations
|
||||
:return: tuple of dictionary with quantities of quant operation and product that can not be matched between ops and moves
|
||||
and dictionary with remaining values on moves
|
||||
|
||||
"""
|
||||
quant_obj = self.pool.get("stock.quant")
|
||||
move_obj = self.pool.get("stock.move")
|
||||
op_obj = self.pool.get("stock.pack.operation")
|
||||
res = {}
|
||||
res2 = {} #what is left from moves
|
||||
for picking in self.browse(cr, uid, picking_ids, context=context):
|
||||
# unreserve everything and initialize res2
|
||||
for move in picking.move_lines:
|
||||
quant_obj.quants_unreserve(cr, uid, move, context=context)
|
||||
res2[move.id] = move.product_qty
|
||||
for ops in picking.pack_operation_ids:
|
||||
#Find moves that correspond
|
||||
if ops.product_id:
|
||||
#TODO: Should have order such that things with lots and packings are searched first
|
||||
move_ids = move_obj.search(cr, uid, [('picking_id','=',picking.id), ('product_id', '=', ops.product_id.id), ('remaining_qty', '>', 0.0)], context=context)
|
||||
qty_to_do = ops.product_qty
|
||||
while qty_to_do > 0 and move_ids:
|
||||
move = move_obj.browse(cr, uid, move_ids.pop(), context=context)
|
||||
if move.remaining_qty > qty_to_do:
|
||||
qty = qty_to_do
|
||||
qty_to_do = 0
|
||||
else:
|
||||
qty = move.remaining_qty
|
||||
qty_to_do -= move.remaining_qty
|
||||
|
||||
if create and move.location_id != 'internal':
|
||||
# Create quants
|
||||
vals = {
|
||||
'product_id': move.product_id.id,
|
||||
'location_id': move.location_id.id,
|
||||
'qty': qty,
|
||||
#'cost': price_unit,
|
||||
'history_ids': [(4, move.id)],
|
||||
'in_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'company_id': move.company_id.id,
|
||||
'lot_id': ops.lot_id and ops.lot_id.id or False,
|
||||
'reservation_id': move.id, #Reserve at once
|
||||
'package_id': ops.result_package_id and ops.result_package_id.id or False,
|
||||
}
|
||||
quant_id = quant_obj.create(cr, uid, vals, context=context)
|
||||
else:
|
||||
#Quants get
|
||||
domain = [('reservation_id', '=', False)]
|
||||
prefered_order = op_obj._get_preferred_order(cr, uid, ops.id, context=context)
|
||||
quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_order=prefered_order, context=context)
|
||||
quant_obj.quants_reserve(cr, uid, quants, move, context=context)
|
||||
res2[move.id] -= qty
|
||||
res[ops.id] = {}
|
||||
res[ops.id][ops.product_id.id] = qty_to_do
|
||||
elif ops.package_id:
|
||||
quants = ops.package_id.quant_ids #_get_package_lines
|
||||
for quant in quants:
|
||||
move_ids = move_obj.search(cr, uid, [('picking_id', '=', picking.id), ('product_id', '=', quant.product_id.id), ('remaining_qty', '>', 0.0)])
|
||||
qty_to_do = quant.qty
|
||||
while qty_to_do > 0 and move_ids:
|
||||
move = move_obj.browse(cr, uid, move_ids.pop(), context=context)
|
||||
if move.remaining_qty > qty_to_do:
|
||||
qty = qty_to_do
|
||||
qty_to_do = 0.0
|
||||
else:
|
||||
qty = move.remaining_qty
|
||||
qty_to_do -= move.remaining_qty
|
||||
quant_obj.quants_reserve(cr, uid, [(quant.id, qty)], move, context=context)
|
||||
res2[move.id] -= qty
|
||||
res.setdefault(ops.id, {}).setdefault(quant.product_id.id, 0.0)
|
||||
res[ops.id][quant.product_id.id] += qty_to_do
|
||||
return (res, res2)
|
||||
|
||||
|
||||
def do_partial(self, cr, uid, picking_ids, context=None):
|
||||
"""
|
||||
If no pack operation, we close the whole move
|
||||
Otherwise, do the pack operations
|
||||
"""
|
||||
#TODO: this variable should be in argument
|
||||
quant_obj = self.pool.get('stock.quant')
|
||||
quant_package_obj = self.pool.get('stock.quant.package')
|
||||
stock_move_obj = self.pool.get('stock.move')
|
||||
for picking in self.browse(cr, uid, picking_ids, context=context):
|
||||
if not picking.pack_operation_ids:
|
||||
self.action_done(cr, uid, [picking.id], context=context)
|
||||
continue
|
||||
for op in picking.pack_operation_ids:
|
||||
#TODO: op.package_id can not work as quants_get is not defined on quant package => gives traceback
|
||||
if op.package_id:
|
||||
for quant in quant_package_obj.quants_get(cr, uid, op.package_id, context=context):
|
||||
self._do_partial_product_move(cr, uid, picking, quant.product_id, quant.qty, quant, context=context)
|
||||
op.package_id.write(cr, uid, {
|
||||
'parent_id': op.result_package_id.id
|
||||
}, context=context)
|
||||
elif op.product_id:
|
||||
moves = self._do_partial_product_move(cr, uid, picking, op.product_id, op.product_qty, op.quant_id, context=context)
|
||||
|
||||
quants = []
|
||||
for m in moves:
|
||||
for quant in m.quant_ids:
|
||||
quants.append(quant.id)
|
||||
quant_obj.write(cr, uid, quants, {
|
||||
'package_id': op.result_package_id.id
|
||||
|
||||
}, context=context)
|
||||
else:
|
||||
#First thing that needs to happen is rereserving the quants
|
||||
res = self.rereserve(cr, uid, [picking.id], create = True, context = context) #This time, quants need to be created
|
||||
orig_moves = picking.move_lines
|
||||
#Add moves that operations need extra
|
||||
extra_moves = []
|
||||
for ops in res[0].keys():
|
||||
for prod in res[0][ops].keys():
|
||||
product = self.pool.get('product.product').browse(cr, uid, prod, context=context)
|
||||
qty = res[0][ops][prod]
|
||||
if qty > 0:
|
||||
#TODO: Maybe should try to reserve quants?
|
||||
quant = False
|
||||
move_id = stock_move_obj.create(cr, uid, {
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': qty,
|
||||
'product_uom': product.uom_id.id,
|
||||
'location_id': picking.location_id.id,
|
||||
'location_dest_id': picking.location_dest_id.id,
|
||||
'picking_id': picking.id,
|
||||
'reserved_quant_ids': quant and [(4, quant.id)] or [],
|
||||
'picking_type_id': picking.picking_type_id.id
|
||||
}, context=context)
|
||||
extra_moves.append(move_id)
|
||||
res2 = res[1]
|
||||
for move in res2.keys():
|
||||
if res2[move] > 0:
|
||||
mov = stock_move_obj.browse(cr, uid, move, context=context)
|
||||
newmove_id = stock_move_obj.split(cr, uid, mov, res2[move], context=context)
|
||||
|
||||
stock_move_obj.action_done(cr, uid, extra_moves + [x.id for x in orig_moves], context=context)
|
||||
picking.refresh()
|
||||
self._create_backorder(cr, uid, picking, context=context)
|
||||
return True
|
||||
|
||||
@@ -884,20 +994,29 @@ class stock_move(osv.osv):
|
||||
res[m.id] = uom_obj._compute_qty_obj(cr, uid, m.product_uom, m.product_uom_qty, m.product_id.uom_id)
|
||||
return res
|
||||
|
||||
# def _get_remaining_qty(self, cr, uid, ids, field_name, args, context=None):
|
||||
# #TODO: this function assumes that there aren't several stock move in the same picking with the same product. what should we do in that case?
|
||||
# #TODO take care of the quant on stock moves too
|
||||
# res = dict.fromkeys(ids, False)
|
||||
# for move in self.browse(cr, uid, ids, context=context):
|
||||
# res[move.id] = move.product_qty
|
||||
# if move.picking_id:
|
||||
# for op in move.picking_id.pack_operation_ids:
|
||||
# if op.product_id == move.product_id or (op.quant_id and op.quant_id.product_id == move.product_id):
|
||||
# res[move.id] -= op.product_qty
|
||||
# if op.package_id:
|
||||
# #find the product qty recursively
|
||||
# res[move.id] -= self.pool.get('stock.quant.package')._get_product_total_qty(cr, uid, op.package_id, move.product_id.id, context=context)
|
||||
# return res
|
||||
|
||||
def _get_remaining_qty(self, cr, uid, ids, field_name, args, context=None):
|
||||
#TODO: this function assumes that there aren't several stock move in the same picking with the same product. what should we do in that case?
|
||||
#TODO take care of the quant on stock moves too
|
||||
res = dict.fromkeys(ids, False)
|
||||
res = {}
|
||||
for move in self.browse(cr, uid, ids, context=context):
|
||||
res[move.id] = move.product_qty
|
||||
if move.picking_id:
|
||||
for op in move.picking_id.pack_operation_ids:
|
||||
if op.product_id == move.product_id or (op.quant_id and op.quant_id.product_id == move.product_id):
|
||||
res[move.id] -= op.product_qty
|
||||
if op.package_id:
|
||||
#find the product qty recursively
|
||||
res[move.id] -= self.pool.get('stock.quant.package')._get_product_total_qty(cr, uid, op.package_id, move.product_id.id, context=context)
|
||||
res[move.id] = move.product_uom_qty
|
||||
for quant in move.reserved_quant_ids:
|
||||
res[move.id] -= quant.qty
|
||||
return res
|
||||
|
||||
|
||||
def _get_lot_ids(self, cr, uid, ids, field_name, args, context=None):
|
||||
res = dict.fromkeys(ids, False)
|
||||
@@ -923,6 +1042,14 @@ class stock_move(osv.osv):
|
||||
res[move.id] = min(move.product_qty, availability)
|
||||
return res
|
||||
|
||||
|
||||
def _get_move(self, cr, uid, ids, context=None):
|
||||
res = set()
|
||||
for quant in self.browse(cr, uid, ids, context=context):
|
||||
if quant.reservation_id:
|
||||
res.add(quant.reservation_id.id)
|
||||
return list(res)
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Description', required=True, select=True),
|
||||
'priority': fields.selection([('0', 'Not urgent'), ('1', 'Urgent')], 'Priority'),
|
||||
@@ -988,7 +1115,10 @@ class stock_move(osv.osv):
|
||||
|
||||
'quant_ids': fields.many2many('stock.quant', 'stock_quant_move_rel', 'move_id', 'quant_id', 'Quants'),
|
||||
'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_id', 'Reserved quants'),
|
||||
'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]}),
|
||||
'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity',
|
||||
digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]},
|
||||
store = {'stock.move': (lambda self, cr, uid, ids, c={}: ids , ['product_qty', 'product_uom', 'reserved_quant_ids'], 10),
|
||||
'stock.quant': (_get_move, ['reservation_id'], 10)}),
|
||||
'procurement_id': fields.many2one('procurement.order', 'Procurement'),
|
||||
'group_id': fields.related('procurement_id', 'group_id', type='many2one', relation="procurement.group", string='Procurement Group'),
|
||||
'rule_id': fields.many2one('procurement.rule', 'Procurement Rule'),
|
||||
@@ -1215,7 +1345,6 @@ class stock_move(osv.osv):
|
||||
'state': 'confirmed',
|
||||
'group_id': move.group_id and move.group_id.id or False,
|
||||
'picking_type_id': move.picking_type_id and move.picking_type_id.id or False,
|
||||
'move_lines': [(6, 0, [move.id])]
|
||||
}
|
||||
pick = pick_obj.create(cr, uid, values, context=context)
|
||||
move.write({'picking_id': pick})
|
||||
@@ -1291,6 +1420,7 @@ class stock_move(osv.osv):
|
||||
for m2 in move.move_orig_ids:
|
||||
for q in m2.quant_ids:
|
||||
dp.append(str(q.id))
|
||||
qty -= q.qty
|
||||
domain = ['|', ('reservation_id', '=', False), ('reservation_id', '=', move.id)]
|
||||
quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_order = dp and ('id not in ('+','.join(dp)+')') or False, context=context)
|
||||
#Will only reserve physical quants, no negative
|
||||
@@ -1336,7 +1466,6 @@ class stock_move(osv.osv):
|
||||
"""
|
||||
context = context or {}
|
||||
quant_obj = self.pool.get("stock.quant")
|
||||
picking_obj = self.pool.get('stock.picking')
|
||||
|
||||
todo = [move.id for move in self.browse(cr, uid, ids, context=context) if move.state == "draft"]
|
||||
if todo:
|
||||
@@ -1353,7 +1482,13 @@ class stock_move(osv.osv):
|
||||
# quant_obj.quants_move(cr, uid, quants, move, location_dest_id, context=context)
|
||||
# should replace the above 2 lines
|
||||
domain = ['|', ('reservation_id', '=', False), ('reservation_id', '=', move.id)]
|
||||
quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_order = 'reservation_id<>'+str(move.id), context=context)
|
||||
prefered_order = 'reservation_id<>' + str(move.id)
|
||||
# if lot_id:
|
||||
# prefered_order = 'lot_id<>' + lot_id + ", " + prefered_order
|
||||
# if pack_id:
|
||||
# prefered_order = 'pack_id<>' + pack_id + ", " + prefered_order
|
||||
quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_order = prefered_order, context=context)
|
||||
|
||||
#Will move all quants_get and as such create negative quants
|
||||
quant_obj.quants_move(cr, uid, quants, move, context=context)
|
||||
quant_obj.quants_unreserve(cr, uid, move, context=context)
|
||||
@@ -1510,7 +1645,7 @@ class stock_move(osv.osv):
|
||||
self.write(cr, uid, [move.id], {
|
||||
'product_uom_qty': move.product_uom_qty - uom_qty,
|
||||
'product_uos_qty': move.product_uos_qty - uos_qty,
|
||||
'reserved_quant_ids': [(6,0,[])]
|
||||
# 'reserved_quant_ids': [(6,0,[])] SHOULD NOT CHANGE as it has been reserved already
|
||||
}, context=context)
|
||||
return new_move
|
||||
|
||||
@@ -1945,6 +2080,19 @@ class stock_pack_operation(osv.osv):
|
||||
'date': fields.date.context_today,
|
||||
}
|
||||
|
||||
def _get_preferred_order(self, cr, uid, id, context=None):
|
||||
ops = self.browse(cr, uid, id, context=context)
|
||||
res = ""
|
||||
if ops.package_id:
|
||||
res += "package_id <> " + str(ops.package_id.id)
|
||||
if ops.lot_id:
|
||||
if res:
|
||||
res += ", lot_id <> " + str(ops.lot_id.id)
|
||||
else:
|
||||
res += "lot_id <> " + str(ops.lot_id.id)
|
||||
return res
|
||||
|
||||
|
||||
#TODO: this function can be refactored
|
||||
def _search_and_increment(self, cr, uid, picking_id, key, context=None):
|
||||
'''Search for an operation on an existing key in a picking, if it exists increment the qty (+1) otherwise create it
|
||||
|
||||
@@ -583,6 +583,7 @@
|
||||
<button name="force_assign" states="confirmed" string="Force Availability" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="do_partial" states="assigned" string="Transfer Done" groups="stock.group_stock_user" type="object" class="oe_highlight"/>
|
||||
<button name="do_split" string="Postpone Partial Transfer" groups="stock.group_stock_user" type="object" attrs="{'invisible': ['|',('pack_operation_exist', '=', False),('state','!=','assigned')]}"/>
|
||||
<button name="do_rereserve" string="ReReserve from Operations" groups="stock.group_stock_user" type="object" attrs="{'invisible': ['|',('pack_operation_exist', '=', False),('state','!=','assigned')]}"/>
|
||||
<button name="do_prepare_partial" string="Partial Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight" attrs="{'invisible': ['|',('pack_operation_exist', '=', True),('state','!=','assigned')]}"/>
|
||||
<button name="%(act_stock_return_picking)d" string="Reverse Transfer" states="done" type="action" groups="base.group_user"/>
|
||||
<button name="action_cancel" states="assigned,confirmed,draft" string="Cancel Transfer" groups="base.group_user" type="object"/>
|
||||
@@ -803,7 +804,7 @@
|
||||
<field name="scrapped" invisible="1"/>
|
||||
<field name="location_dest_id" groups="stock.group_locations"/>
|
||||
<field name="state"/>
|
||||
<field name="availability"/>
|
||||
<field name="remaining_qty" attrs="{'invisible': [('state', '=', 'done')]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-
|
||||
Create a new stockable product
|
||||
Create a new stockable product
|
||||
-
|
||||
!record {model: product.product, id: packingtest}:
|
||||
name: nice product
|
||||
@@ -8,11 +8,61 @@
|
||||
-
|
||||
!record{model: stock.picking}: |
|
||||
-
|
||||
Create move linked to picking
|
||||
Confirm and assign picking and prepare partial
|
||||
-
|
||||
!record{model:stock.move, id:}
|
||||
-
|
||||
Confirm picking
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
!python {model: stock.picking, id:}: |
|
||||
self.action_confirm(cr, uid, ref())
|
||||
-
|
||||
Put 120 pieces on Pallet 1 (package), 120 pieces on Pallet 2 with lot A and 60 pieces on Pallet 3
|
||||
-
|
||||
!python {model: stock.picking, id:} |
|
||||
#Change quantity of first to 120 and create 2 others quant operations
|
||||
#Create package for each line and assign it as result_package_id
|
||||
-
|
||||
Use button rereserve and check the qtyremaining on the moves are correct (=0)
|
||||
-
|
||||
|
||||
-
|
||||
Transfer the reception
|
||||
-
|
||||
|
||||
-
|
||||
Check the system created 3 quants one with 120 pieces on pallet 1, one with 120 pieces on pallet 2 with lot A and 60 pieces on pallet 3
|
||||
-
|
||||
|
||||
-
|
||||
Check there is no backorder or extra moves created
|
||||
-
|
||||
|
||||
-
|
||||
Make a delivery order of 300 pieces to the customer
|
||||
-
|
||||
|
||||
-
|
||||
Assign and confirm
|
||||
-
|
||||
|
||||
-
|
||||
Instead of doing the 300 pieces, you decide to take pallet 1 (do not mention product in operation here) and 20 pieces from lot A and 10 pieces from pallet 3
|
||||
-
|
||||
|
||||
-
|
||||
Process this picking
|
||||
-
|
||||
|
||||
-
|
||||
Check the quants that you have 120 pieces pallet 1 in customers, 100 pieces pallet 2 in stock and 20 with customers and 50 in stock, 10 in customers from pallet 3
|
||||
-
|
||||
|
||||
-
|
||||
Check a backorder was created and on that backorder, prepare partial and add an op with 20 pieces (20 that cannot be assigned) with lot B
|
||||
-
|
||||
|
||||
-
|
||||
Process this backorder
|
||||
-
|
||||
|
||||
-
|
||||
Check you have a negative quant because there were 20 too many that were transferred with lot B
|
||||
-
|
||||
|
||||
@@ -38,7 +38,7 @@ This adds a route on the sales order and sales order line (mini module)
|
||||
'init_xml': [],
|
||||
'update_xml': ['stock_complex_routes.xml'],
|
||||
'demo_xml': [],
|
||||
'test': [],
|
||||
'test': ['test/lifo_price.yml'],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
}
|
||||
|
||||
+20
-16
@@ -1,8 +1,12 @@
|
||||
-
|
||||
Set product category removal strategy as LIFO
|
||||
Set product category removal strategy as LIFO
|
||||
-
|
||||
!record {model: product.category, id: product.product_category_1}:
|
||||
removal_strategy: lifo
|
||||
!record {model: product.category, id: product.product_category_001}:
|
||||
name : Lifo Category
|
||||
removal_strategy_ids:
|
||||
- product_categ_id: product.product_category_1
|
||||
method: lifo
|
||||
location_id: stock.stock_location_stock
|
||||
-
|
||||
Set a product as using lifo price
|
||||
-
|
||||
@@ -58,10 +62,9 @@
|
||||
-
|
||||
Process the reception of purchase order 1
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lifo1")).picking_ids
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
!python {model: stock.picking}: |
|
||||
order = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lifo1")).picking_ids[0]
|
||||
order.do_partial(context=context)
|
||||
-
|
||||
Check the standard price of the product (lifo icecream)
|
||||
-
|
||||
@@ -74,10 +77,9 @@
|
||||
-
|
||||
Process the reception of purchase order 2
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lifo2")).picking_ids
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
!python {model: stock.picking}: |
|
||||
order = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_lifo2")).picking_ids[0]
|
||||
order.do_partial(context=context)
|
||||
-
|
||||
Check the standard price should not have changed
|
||||
-
|
||||
@@ -87,7 +89,7 @@
|
||||
Let us send some goods
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_lifo_shipment}:
|
||||
type: out
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
Picking needs movement from stock
|
||||
-
|
||||
@@ -95,8 +97,10 @@
|
||||
picking_id: outgoing_lifo_shipment
|
||||
product_id: product_lifo_icecream
|
||||
product_uom: product.product_uom_kgm
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
product_qty: 20.0
|
||||
type: out
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
I assign this outgoing shipment
|
||||
-
|
||||
@@ -105,9 +109,9 @@
|
||||
-
|
||||
Process the delivery of the outgoing shipment
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model': 'stock.picking','active_ids': [ref("outgoing_lifo_shipment")], 'default_type':'out'})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
!python {model: stock.picking}: |
|
||||
pick_order = self.pool.get('stock.picking').browse(cr, uid, ref("outgoing_lifo_shipment"))
|
||||
pick_order.do_partial(context=context)
|
||||
-
|
||||
Check standard price became 80 euro
|
||||
-
|
||||
@@ -15,7 +15,7 @@
|
||||
product_uom: product.product_uom_unit
|
||||
location_dest_id: stock.stock_location_customers
|
||||
move_type: direct
|
||||
type: out
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
I need to check the availability of the product so I make my picking order for processing later.
|
||||
-
|
||||
|
||||
@@ -215,7 +215,7 @@ class product_category(osv.osv):
|
||||
_inherit = 'product.category'
|
||||
_columns = {
|
||||
'route_ids': fields.many2many('stock.location.route', 'stock_location_route_categ', 'categ_id', 'route_id', 'Routes'),
|
||||
#'removal_strategy_ids': fields.one2many('product.removal', 'product_categ_id', 'Removal Strategies'),
|
||||
'removal_strategy_ids': fields.one2many('product.removal', 'product_categ_id', 'Removal Strategies'),
|
||||
#'putaway_strategy_ids': fields.one2many('product.putaway', 'product_categ_id', 'Put Away Strategies'),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user