-"""
-fields_msg = {}
-
-def _createInvoices(self, cr, uid, data, context={}):
- list_inv = []
- pool_obj = pooler.get_pool(cr.dbname)
- obj_sale = pool_obj.get('sale.order')
- data_sale = obj_sale.browse(cr,uid,data['ids'])
- obj_lines = pool_obj.get('account.invoice.line')
- for sale in data_sale:
- address_contact = False
- address_invoice = False
- create_ids = []
- ids_inv = []
- if sale.order_policy == 'postpaid':
- raise osv.except_osv(
- _('Error'),
- _("You cannot make an advance on a sale order that is defined as 'Automatic Invoice after delivery'."))
- val = obj_lines.product_id_change(cr, uid, [], data['form']['product_id'],uom = False, partner_id = sale.partner_id.id, fposition_id=sale.fiscal_position.id)
- line_id =obj_lines.create(cr, uid, {
- 'name': val['value']['name'],
- 'account_id':val['value']['account_id'],
- 'price_unit': data['form']['amount'],
- 'quantity': data['form']['qtty'],
- 'discount': False,
- 'uos_id': val['value']['uos_id'],
- 'product_id':data['form']['product_id'],
- 'invoice_line_tax_id': [(6,0,val['value']['invoice_line_tax_id'])],
- 'account_analytic_id': sale.project_id and sale.project_id.id or False,
- 'note':'',
- })
- create_ids.append(line_id)
- inv = {
- 'name': sale.name,
- 'origin': sale.name,
- 'type': 'out_invoice',
- 'reference': False,
- 'account_id': sale.partner_id.property_account_receivable.id,
- 'partner_id': sale.partner_id.id,
- 'address_invoice_id':sale.partner_invoice_id.id,
- 'address_contact_id':sale.partner_order_id.id,
- 'invoice_line': [(6,0,create_ids)],
- 'currency_id' :sale.pricelist_id.currency_id.id,
- 'comment': '',
- 'payment_term':sale.payment_term.id,
- 'fiscal_position': sale.partner_id.property_account_position.id
- }
- inv_obj = pool_obj.get('account.invoice')
- inv_id = inv_obj.create(cr, uid, inv)
-
- for inv in sale.invoice_ids:
- ids_inv.append(inv.id)
- ids_inv.append(inv_id)
- obj_sale.write(cr,uid,sale.id,{'invoice_ids':[(6,0,ids_inv)]})
- list_inv.append(inv_id)
-#
-# If invoice on picking: add the cost on the SO
-# If not, the advance will be deduced when generating the final invoice
-#
- if sale.order_policy=='picking':
- pool_obj.get('sale.order.line').create(cr, uid, {
- 'order_id': sale.id,
- 'name': val['value']['name'],
- 'price_unit': -data['form']['amount'],
- 'product_uom_qty': data['form']['qtty'],
- 'product_uos_qty': data['form']['qtty'],
- 'product_uos': val['value']['uos_id'],
- 'product_uom': val['value']['uos_id'],
- 'product_id':data['form']['product_id'],
- 'discount': False,
- 'tax_id': [(6,0,val['value']['invoice_line_tax_id'])],
- }, context)
- return {'invoice_ids':list_inv}
-
-class sale_advance_payment(wizard.interface):
- def _open_invoice(self, cr, uid, data, context):
- pool_obj = pooler.get_pool(cr.dbname)
- mod_obj = pool_obj.get('ir.model.data')
- result = mod_obj._get_id(cr, uid, 'account', 'view_account_invoice_filter')
- id = mod_obj.read(cr, uid, result, ['res_id'])
- model_data_ids = pool_obj.get('ir.model.data').search(cr,uid,[('model','=','ir.ui.view'),('name','=','invoice_form')])
- resource_id = pool_obj.get('ir.model.data').read(cr,uid,model_data_ids,fields=['res_id'])[0]['res_id']
- return {
- 'domain': "[('id','in', ["+','.join(map(str,data['form']['invoice_ids']))+"])]",
- 'name': 'Invoices',
- 'view_type': 'form',
- 'view_mode': 'tree,form',
- 'res_model': 'account.invoice',
- 'views': [(False,'tree'),(resource_id,'form')],
- 'context': "{'type':'out_invoice'}",
- 'type': 'ir.actions.act_window',
- 'search_view_id': id['res_id']
- }
-
- states = {
- 'init' : {
- 'actions' : [],
- 'result' : {'type' : 'form' , 'arch' : form,'fields' : fields,'state' : [('end','Cancel','gtk-cancel'),('create','Create Advance Invoice','gtk-ok')]}
- },
- 'create': {
- 'actions': [_createInvoices],
- 'result': {'type' : 'form' ,'arch' : form_msg,'fields' : fields_msg, 'state':[('end','Close','gtk-close'),('open','Open Advance Invoice','gtk-open')]}
- },
- 'open': {
- 'actions': [],
- 'result': {'type':'action', 'action':_open_invoice, 'state':'end'}
- }
- }
-
-sale_advance_payment("sale.advance_payment_inv")
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/addons/sale/wizard/sale_line_invoice.py b/addons/sale/wizard/sale_line_invoice.py
new file mode 100644
index 00000000000..4bf3074db54
--- /dev/null
+++ b/addons/sale/wizard/sale_line_invoice.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+from osv import fields, osv
+from service import web_services
+import wizard
+import netsvc
+import ir
+import pooler
+
+class sale_order_line_make_invoice(osv.osv_memory):
+ _name = "sale.order.line.make.invoice"
+ _description = "Sale OrderLine Make_invoice"
+ _columns = {
+ 'grouped': fields.boolean('Group the invoices'),
+ }
+ _default = {
+ 'grouped' : lambda *a: False
+ }
+
+ def make_invoices(self, cr, uid, ids, context):
+ """
+ To make invoices.
+
+ @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
+ @param context: A standard dictionary
+
+ @return: A dictionary which of fields with values.
+
+ """
+
+ res = False
+ invoices = {}
+
+ #TODO: merge with sale.py/make_invoice
+ def make_invoice(order, lines):
+ """
+ To make invoices.
+
+ @param order:
+ @param lines:
+
+ @return:
+
+ """
+ a = order.partner_id.property_account_receivable.id
+ if order.partner_id and order.partner_id.property_payment_term.id:
+ pay_term = order.partner_id.property_payment_term.id
+ else:
+ pay_term = False
+ inv = {
+ 'name': order.name,
+ 'origin': order.name,
+ 'type': 'out_invoice',
+ 'reference': "P%dSO%d" % (order.partner_id.id, order.id),
+ 'account_id': a,
+ 'partner_id': order.partner_id.id,
+ 'address_invoice_id': order.partner_invoice_id.id,
+ 'address_contact_id': order.partner_invoice_id.id,
+ 'invoice_line': [(6,0,lines)],
+ 'currency_id' : order.pricelist_id.currency_id.id,
+ 'comment': order.note,
+ 'payment_term': pay_term,
+ 'fiscal_position': order.partner_id.property_account_position.id
+ }
+ inv_id = self.pool.get('account.invoice').create(cr, uid, inv)
+ return inv_id
+
+ sales_order_line_obj = self.pool.get('sale.order.line')
+ sales_order_obj = self.pool.get('sale.order')
+ wf_service = netsvc.LocalService('workflow')
+ for line in sales_order_line_obj.browse(cr,uid,context['active_ids']):
+ if (not line.invoiced) and (line.state not in ('draft','cancel')):
+ if not line.order_id.id in invoices:
+ invoices[line.order_id.id] = []
+ line_id = sales_order_line_obj.invoice_line_create(cr, uid,
+ [line.id])
+ for lid in line_id:
+ invoices[line.order_id.id].append((line, lid))
+ sales_order_line_obj.write(cr, uid, [line.id],
+ {'invoiced': True})
+ flag = True
+ data_sale = sales_order_obj.browse(cr,uid,line.order_id.id)
+ for line in data_sale.order_line:
+ if not line.invoiced:
+ flag = False
+ break
+ if flag:
+ wf_service.trg_validate(uid, 'sale.order', line.order_id.id, 'all_lines', cr)
+ sales_order_obj.write(cr,uid,[line.order_id.id],{'state' : 'progress'})
+
+ for result in invoices.values():
+ order = result[0][0].order_id
+ il = map(lambda x: x[1], result)
+ res = make_invoice(order, il)
+ cr.execute('INSERT INTO sale_order_invoice_rel \
+ (order_id,invoice_id) values (%s,%s)', (order.id, res))
+ return {}
+
+
+sale_order_line_make_invoice()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/sale/wizard/sale_line_invoice.xml b/addons/sale/wizard/sale_line_invoice.xml
new file mode 100644
index 00000000000..a9ea13ea900
--- /dev/null
+++ b/addons/sale/wizard/sale_line_invoice.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ Sale OrderLine Make Invoice
+ sale.order.line.make.invoice
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/sale/wizard/sale_make_invoice_advance.py b/addons/sale/wizard/sale_make_invoice_advance.py
new file mode 100644
index 00000000000..6c9ca17b836
--- /dev/null
+++ b/addons/sale/wizard/sale_make_invoice_advance.py
@@ -0,0 +1,180 @@
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+from osv import fields, osv
+from service import web_services
+from tools.translate import _
+import ir
+import netsvc
+import pooler
+import wizard
+
+
+class sale_advance_payment_inv(osv.osv_memory):
+ _name = "sale.advance.payment.inv"
+ _description = "Sale Advance Payment Invoice"
+ _columns = {
+ 'product_id': fields.many2one('product.product', 'Product', required=True),
+ 'amount': fields.float('Unit Price', size=(16, 2), required=True),
+ 'qtty': fields.float('Quantity', size=(16, 2), required=True),
+ }
+ _default = {
+ 'qtty' : lambda *a: 1
+ }
+ def create_invoices(self, cr, uid, ids, context={}):
+ """
+ To create invoices.
+
+ @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:
+
+ """
+ list_inv = []
+ obj_sale = self.pool.get('sale.order')
+ obj_lines = self.pool.get('account.invoice.line')
+ inv_obj = self.pool.get('account.invoice')
+
+ for sale_adv_obj in self.browse(cr, uid, ids):
+ for sale in obj_sale.browse(cr, uid, context['active_ids']):
+ address_contact = False
+ address_invoice = False
+ create_ids = []
+ ids_inv = []
+ if sale.order_policy == 'postpaid':
+ raise osv.except_osv(
+ _('Error'),
+ _("You cannot make an advance on a sale order \
+ that is defined as 'Automatic Invoice after delivery'."))
+ val = obj_lines.product_id_change(cr, uid, [], sale_adv_obj.product_id.id,
+ uom = False, partner_id = sale.partner_id.id, fposition_id=sale.fiscal_position.id)
+ line_id =obj_lines.create(cr, uid, {
+ 'name': val['value']['name'],
+ 'account_id': val['value']['account_id'],
+ 'price_unit': sale_adv_obj.amount,
+ 'quantity': sale_adv_obj.qtty,
+ 'discount': False,
+ 'uos_id': val['value']['uos_id'],
+ 'product_id': sale_adv_obj.product_id.id,
+ 'invoice_line_tax_id': [(6, 0, val['value']['invoice_line_tax_id'])],
+ 'account_analytic_id': sale.project_id.id or False,
+ #'note':'',
+ })
+ create_ids.append(line_id)
+ inv = {
+ 'name': sale.name,
+ 'origin': sale.name,
+ 'type': 'out_invoice',
+ 'reference': False,
+ 'account_id': sale.partner_id.property_account_receivable.id,
+ 'partner_id': sale.partner_id.id,
+ 'address_invoice_id':sale.partner_invoice_id.id,
+ 'address_contact_id':sale.partner_order_id.id,
+ 'invoice_line': [(6, 0, create_ids)],
+ 'currency_id' :sale.pricelist_id.currency_id.id,
+ 'comment': '',
+ 'payment_term':sale.payment_term.id,
+ 'fiscal_position': sale.partner_id.property_account_position.id
+ }
+
+ inv_id = inv_obj.create(cr, uid, inv)
+
+ for inv in sale.invoice_ids:
+ ids_inv.append(inv.id)
+ ids_inv.append(inv_id)
+ obj_sale.write(cr, uid, sale.id, {'invoice_ids':[(6, 0, ids_inv)]})
+ list_inv.append(inv_id)
+ #
+ # If invoice on picking: add the cost on the SO
+ # If not, the advance will be deduced when generating the final invoice
+ #
+ if sale.order_policy=='picking':
+ self.pool.get('sale.order.line').create(cr, uid, {
+ 'order_id': sale.id,
+ 'name': val['value']['name'],
+ 'price_unit': -sale_adv_obj.amount,
+ 'product_uom_qty': sale_adv_obj.qtty,
+ 'product_uos_qty': sale_adv_obj.qtty,
+ 'product_uos': val['value']['uos_id'],
+ 'product_uom': val['value']['uos_id'],
+ 'product_id': sale_adv_obj.product_id.id,
+ 'discount': False,
+ 'tax_id': [(6, 0, val['value']['invoice_line_tax_id'])],
+ }, context)
+
+ return {#'invoice_ids':list_inv,
+ 'name': 'Open Invoice',
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'sale.open.invoice',
+ 'type': 'ir.actions.act_window',
+ 'target': 'new',
+ 'context': "{'invoice_ids'=%s}" % (list_inv)
+ }
+
+sale_advance_payment_inv()
+
+class sale_open_invoice(osv.osv_memory):
+ _name = "sale.open.invoice"
+ _description = "Sale Open Invoice"
+ _columns = {
+ }
+
+ def open_invoice(self, cr, uid, ids, context):
+ """
+ To open invoice.
+
+ @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:
+
+ """
+ mod_obj = self.pool.get('ir.model.data')
+ invoices = []
+ #TODO: Can not get invoice ids here
+ for advance_pay in self.browse(cr, uid, ids):
+ result = mod_obj._get_id(cr, uid, 'account', 'view_account_invoice_filter')
+ id = mod_obj.read(cr, uid, result, ['res_id'])
+ model_data_ids = mod_obj.search(cr, uid,
+ [('model', '=', 'ir.ui.view'), ('name', '=', 'invoice_form')])
+ resource_id = mod_obj.read(cr, uid, model_data_ids,
+ fields=['res_id'])[0]['res_id']
+ return {
+# 'domain': "[('id','in', ["+','.join(map(str, invoices))+"])]", # TODO
+ 'name': 'Invoices',
+ 'view_type': 'form',
+ 'view_mode': 'tree,form',
+ 'res_model': 'account.invoice',
+ 'views': [(False, 'tree'), (resource_id, 'form')],
+ 'context': "{'type':'out_invoice'}",
+ 'type': 'ir.actions.act_window',
+ 'search_view_id': id['res_id']
+ }
+sale_open_invoice()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/sale/wizard/sale_make_invoice_advance.xml b/addons/sale/wizard/sale_make_invoice_advance.xml
new file mode 100644
index 00000000000..5fc8b993921
--- /dev/null
+++ b/addons/sale/wizard/sale_make_invoice_advance.xml
@@ -0,0 +1,59 @@
+
+
+
+
+ Sale Advance Payment Invoive
+ sale.advance.payment.inv
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Open Invoice
+ sale.open.invoice
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Open Invoice
+ ir.actions.act_window
+ sale.open.invoice
+ form
+ form
+ new
+
+
+
diff --git a/addons/sale/wizard/wizard_sale_line_invoice.py b/addons/sale/wizard/wizard_sale_line_invoice.py
deleted file mode 100644
index 8e1e49ab22d..00000000000
--- a/addons/sale/wizard/wizard_sale_line_invoice.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# 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 .
-#
-##############################################################################
-
-import wizard
-import netsvc
-import ir
-import pooler
-
-invoice_form = """
-
-
+ stock.move.search2stock.movesearch
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
-
+ Incoming Productsstock.moveir.actions.act_window
@@ -1513,17 +1584,17 @@
-
- tree
-
-
-
+
+ tree
+
+
+
-
- form
-
-
-
+
+ form
+
+
+
@@ -1606,82 +1677,132 @@
tree_but_openstock.locationOpen Products
-
+
-
-
-
- Products To Received
- report.products.to.received.planned
- tree
-
-
-
-
-
-
-
-
+
+
+
+ Products To Received
+ report.products.to.received.planned
+ tree
+
+
+
+
+
+
+
+
-
- Products To Received
- report.products.to.received.planned
- graph
-
-
-
-
-
-
-
-
+
+ Products To Received
+ report.products.to.received.planned
+ graph
+
+
+
+
+
+
+
+
-
- Number of Products to receive Vs planned
- report.products.to.received.planned
- ir.actions.act_window
- form
- graph,tree
-
-
+
+ Number of Products to receive Vs planned
+ report.products.to.received.planned
+ ir.actions.act_window
+ form
+ graph,tree
+
+
-
-
- Delivery Products
- report.delivery.products.planned
- tree
-
-
-
-
-
-
-
-
+
+
+ Delivery Products
+ report.delivery.products.planned
+ tree
+
+
+
+
+
+
+
+
-
- Delivery Products
- report.delivery.products.planned
- graph
-
-
-
-
-
-
-
-
-
-
- Delivery Products Vs Planned
- report.delivery.products.planned
- ir.actions.act_window
- form
- graph,tree
-
-
+
+ Delivery Products
+ report.delivery.products.planned
+ graph
+
+
+
+
+
+
+
+
+
+ Delivery Products Vs Planned
+ report.delivery.products.planned
+ ir.actions.act_window
+ form
+ graph,tree
+
+
+
+
+ stock.delivery.from
+ stock.delivery
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stock.delivery.tree
+ stock.delivery
+ tree
+
+
+
+
+
+
+
+
+
+
+
+ Delivered Products
+ stock.delivery
+ ir.actions.act_window
+ form
+ form,tree
+
+
+
+
diff --git a/addons/stock/stock_wizard.xml b/addons/stock/stock_wizard.xml
index 5db1269d42c..f9a2dfc93b0 100644
--- a/addons/stock/stock_wizard.xml
+++ b/addons/stock/stock_wizard.xml
@@ -13,20 +13,23 @@
multi="True"
name="stock.move.split"
string="Split move line"/>
+
-
+
-
+
-
-
-
-
-
-
-
-
-
+ id="wizard_merge_inventory"/-->
-
-
-
-
-
+
diff --git a/addons/stock/wizard/__init__.py b/addons/stock/wizard/__init__.py
index 6a15449a151..6f5a83a07b3 100644
--- a/addons/stock/wizard/__init__.py
+++ b/addons/stock/wizard/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
-#
+#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL ().
#
@@ -15,24 +15,26 @@
# 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 .
+# along with this program. If not, see .
#
##############################################################################
import stock_traceability
-import wizard_partial_picking
-import wizard_partial_move
+import stock_move
+import stock_partial_picking
+import stock_partial_move
import wizard_picking_make
import wizard_replacement
import wizard_return
import wizard_split_lot_line
import wizard_ups
-import wizard_invoice_onshipping
-import wizard_location_product
-import wizard_inventory
-import wizard_split_inventory_lots
-import inventory_merge_zero
import inventory_merge
+import stock_inventory_set_stock_zero
+import stock_fill_inventory
+import stock_inventory_line_split
+import stock_invoice_onshipping
+import stock_location_product
+import stock_change_standard_price
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/stock/wizard/inventory_merge.py b/addons/stock/wizard/inventory_merge.py
deleted file mode 100644
index 7be8eb2b3e3..00000000000
--- a/addons/stock/wizard/inventory_merge.py
+++ /dev/null
@@ -1,98 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# 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 .
-#
-##############################################################################
-
-
-import wizard
-import pooler
-from tools.translate import _
-
-
-_form = """
-
-
-
-
-"""
-
-
-def do_merge(self, cr, uid, data, context):
-
- invent_obj = pooler.get_pool(cr.dbname).get('stock.inventory')
- invent_line_obj = pooler.get_pool(cr.dbname).get('stock.inventory.line')
-
- invent_lines = {}
-
- if len(data['ids']) < 2:
- raise wizard.except_wizard(_('Warning'),
- _('Please select at least two inventories.'))
-
-
-
- for inventory in invent_obj.browse(cr, uid, data['ids'], context=context):
- if inventory.state == "done":
- raise wizard.except_wizard(_('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)
- 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': key[2],
- 'product_qty': quantity,
- })
-
- return {}
-
-
-class merge_inventory(wizard.interface):
- states = {
- 'init' : {
- 'actions' : [],
- 'result' : {'type' : 'form',
- 'arch' : _form,
- 'fields' : {},
- 'state' : [('end', 'Cancel', 'gtk-cancel'),
- ('merge', 'Yes', 'gtk-apply', True) ]}
- },
- 'merge' : {
- 'actions' : [],
- 'result' : {'type' : 'action',
- 'action': do_merge,
- 'state' : 'end'}
- },
- }
-merge_inventory("inventory.merge")
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/addons/stock/wizard/inventory_merge_zero.py b/addons/stock/wizard/inventory_merge_zero.py
deleted file mode 100644
index 3aa2b1e9fd9..00000000000
--- a/addons/stock/wizard/inventory_merge_zero.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# 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 .
-#
-##############################################################################
-
-
-import wizard
-import pooler
-from tools.translate import _
-
-
-_form = """
-
-
-
-
-
-
-"""
-_inventory_fields = {
- 'location_id' : {
- 'string':'Location',
- 'type':'many2one',
- 'relation':'stock.location',
- 'required':True
- }
-}
-
-
-def do_merge(self, cr, uid, data, context):
- invent_obj = pooler.get_pool(cr.dbname).get('stock.inventory')
- invent_line_obj = pooler.get_pool(cr.dbname).get('stock.inventory.line')
- prod_obj = pooler.get_pool(cr.dbname).get('product.product')
-
- if len(data['ids']) <> 1:
- raise wizard.except_wizard(_('Warning'),
- _('Please select one and only one inventory !'))
-
- loc = str(data['form']['location_id'])
-
- cr.execute('select distinct location_id,product_id from stock_inventory_line where inventory_id=%s', (data['ids'][0],))
- inv = cr.fetchall()
- cr.execute('select distinct product_id from stock_move where location_dest_id=%s or location_id=%s',(loc,loc,))
- stock = cr.fetchall()
- for s in stock:
- if (loc,s[0]) not in inv:
- p = prod_obj.browse(cr, uid, s[0])
- invent_line_obj.create(cr, uid, {
- 'inventory_id': data['ids'][0],
- 'location_id': loc,
- 'product_id': s[0],
- 'product_uom': p.uom_id.id,
- 'product_qty': 0.0,
- })
- return {}
-
-
-class merge_inventory(wizard.interface):
- states = {
- 'init' : {
- 'actions' : [],
- 'result' : {'type' : 'form',
- 'arch' : _form,
- 'fields' : _inventory_fields,
- 'state' : [('end', 'Cancel', 'gtk-cancel'),
- ('merge', 'Set to Zero', 'gtk-apply', True) ]}
- },
- 'merge' : {
- 'actions' : [],
- 'result' : {'type' : 'action',
- 'action': do_merge,
- 'state' : 'end'}
- },
- }
-merge_inventory("inventory.merge.stock.zero")
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/addons/stock/wizard/stock_change_standard_price.py b/addons/stock/wizard/stock_change_standard_price.py
new file mode 100644
index 00000000000..8e2ec5f555e
--- /dev/null
+++ b/addons/stock/wizard/stock_change_standard_price.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+from osv import fields, osv
+from tools.translate import _
+
+class change_standard_price(osv.osv_memory):
+ _name = "stock.change.standard.price"
+ _description = "Change Standard Price"
+ _columns = {
+ 'new_price': fields.float('Price', required=True),
+ 'stock_account_input':fields.many2one('account.account', 'Stock Input Account'),
+ 'stock_account_output':fields.many2one('account.account', 'Stock Output Account'),
+ 'stock_journal':fields.many2one('account.journal', 'Stock journal', required=True),
+ 'enable_stock_in_out_acc':fields.boolean('Enable Related Account',),
+ }
+
+ def default_get(self, cr, uid, fields, context):
+ """
+ To get default values for the object.
+
+ @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 which we want default values
+ @param context: A standard dictionary
+
+ @return: A dictionary which of fields with values.
+
+ """
+ product_obj = self.pool.get('product.product').browse(cr, uid, context.get('active_id', False))
+ res = super(change_standard_price, self).default_get(cr, uid, fields, context=context)
+
+ stock_input_acc = product_obj.property_stock_account_input and product_obj.property_stock_account_input.id or False
+ if not stock_input_acc:
+ stock_input_acc = product_obj.categ_id.property_stock_account_input_categ and product_obj.categ_id.property_stock_account_input_categ.id or False
+
+ stock_output_acc = product_obj.property_stock_account_output and product_obj.property_stock_account_output.id or False
+ if not stock_output_acc:
+ stock_output_acc = product_obj.categ_id.property_stock_account_output_categ and product_obj.categ_id.property_stock_account_output_categ.id or False
+
+ price = product_obj.standard_price
+ journal_id = product_obj.categ_id.property_stock_journal and product_obj.categ_id.property_stock_journal.id or False
+
+ if 'new_price' in fields:
+ res.update({'new_price': price})
+ if 'stock_account_input' in fields:
+ res.update({'stock_account_input': stock_input_acc})
+ if 'stock_account_output' in fields:
+ res.update({'stock_account_output': stock_output_acc})
+ if 'stock_journal' in fields:
+ res.update({'stock_journal': journal_id})
+ if 'enable_stock_in_out_acc' in fields:
+ res.update({'enable_stock_in_out_acc': True})
+
+
+ return res
+
+ def onchange_price(self, cr, uid, ids, new_price, context = {}):
+ product_obj = self.pool.get('product.product').browse(cr, uid, context.get('active_id', False))
+ price = product_obj.standard_price
+ diff = price - new_price
+ if diff > 0 :
+ return {'value' : {'enable_stock_in_out_acc':True}}
+ else :
+ return {'value' : {'enable_stock_in_out_acc':False}}
+
+ def change_price(self, cr, uid, ids, context):
+ """
+ Changes the Standard Price of Product.
+ And creates an account move accordingly.
+
+ @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:
+
+ """
+ rec_id = context and context.get('active_id', False)
+ assert rec_id, _('Active ID is not set in Context')
+ prod_obj = self.pool.get('product.product')
+ res = self.browse(cr, uid, ids)
+ datas = {
+ 'new_price' : res[0].new_price,
+ 'stock_output_account' : res[0].stock_account_output.id,
+ 'stock_input_account' : res[0].stock_account_input.id,
+ 'stock_journal' : res[0].stock_journal.id
+ }
+ prod_obj.do_change_standard_price(cr, uid, [rec_id], datas, context)
+ return {}
+
+change_standard_price()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/stock/wizard/stock_change_standard_price_view.xml b/addons/stock/wizard/stock_change_standard_price_view.xml
new file mode 100644
index 00000000000..ffca108390d
--- /dev/null
+++ b/addons/stock/wizard/stock_change_standard_price_view.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ Change Standard Price
+ stock.change.standard.price
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Change Standard Price
+ ir.actions.act_window
+ stock.change.standard.price
+ form
+ form
+
+ new
+
+
+
+
diff --git a/addons/stock/wizard/stock_fill_inventory.py b/addons/stock/wizard/stock_fill_inventory.py
new file mode 100644
index 00000000000..c96638c8d52
--- /dev/null
+++ b/addons/stock/wizard/stock_fill_inventory.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+from osv import fields, osv
+from service import web_services
+from tools.misc import UpdateableStr, UpdateableDict
+from tools.translate import _
+import netsvc
+import pooler
+import time
+import wizard
+
+
+class stock_fill_inventory(osv.osv_memory):
+ _name = "stock.fill.inventory"
+ _description = "Fill Inventory"
+ _columns = {
+ 'location_id': fields.many2one('stock.location', 'Location', required=True),
+ 'recursive': fields.boolean("Include all childs for the location"),
+ }
+
+ def fill_inventory(self, cr, uid, ids, context):
+ """
+ To fill stock inventory according to products available in the selected locations..
+
+ @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:
+
+ """
+ inventory_line_obj = self.pool.get('stock.inventory.line')
+ location_obj = self.pool.get('stock.location')
+ product_obj = self.pool.get('product.product')
+ stock_location_obj = self.pool.get('stock.location')
+ for fill_inventory in self.browse(cr, uid, ids):
+ res = {}
+ res_location = {}
+ if fill_inventory.recursive :
+ location_ids = location_obj.search(cr, uid, [('location_id',
+ 'child_of', fill_inventory.location_id.id)])
+ for location in location_ids :
+ res = location_obj._product_get(cr, uid, location)
+ res_location[location] = res
+ else:
+ context.update({'compute_child': False})
+ res = location_obj._product_get(cr, uid,
+ fill_inventory.location_id.id, context=context)
+ res_location[fill_inventory.location_id.id] = res
+
+ product_ids = []
+ for location in res_location.keys():
+ res = res_location[location]
+ for product_id in res.keys():
+ prod = product_obj.browse(cr, uid, [product_id])[0]
+ uom = prod.uom_id.id
+ context.update({'uom': uom})
+ amount = stock_location_obj._product_get(cr, uid,
+ location, [product_id], context=context)[product_id]
+
+ if(amount):
+ line_ids=inventory_line_obj.search(cr, uid,
+ [('inventory_id', '=', context['active_ids']),
+ ('location_id', '=', location),
+ ('product_id', '=', product_id),
+ ('product_uom', '=', uom),
+ ('product_qty', '=', amount)])
+ if not len(line_ids):
+ inventory_line = {'inventory_id': context['active_ids'][0],
+ 'location_id': location,
+ 'product_id': product_id,
+ 'product_uom': uom,
+ 'product_qty': amount}
+ inventory_line_obj.create(cr, uid, inventory_line)
+ product_ids.append(product_id)
+
+ if(len(product_ids) == 0):
+ raise osv.except_osv(_('Message !'), _('No product in this location.'))
+ return {}
+
+stock_fill_inventory()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/stock/wizard/stock_fill_inventory_view.xml b/addons/stock/wizard/stock_fill_inventory_view.xml
new file mode 100644
index 00000000000..ef95a3bf447
--- /dev/null
+++ b/addons/stock/wizard/stock_fill_inventory_view.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ Fill Inventory
+ stock.fill.inventory
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/stock/wizard/stock_inventory_line_split.py b/addons/stock/wizard/stock_inventory_line_split.py
new file mode 100644
index 00000000000..9fe71582c7f
--- /dev/null
+++ b/addons/stock/wizard/stock_inventory_line_split.py
@@ -0,0 +1,117 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+from osv import fields, osv
+from service import web_services
+from tools.misc import UpdateableStr, UpdateableDict
+from tools.translate import _
+import netsvc
+import pooler
+import time
+import wizard
+
+class stock_inventory_line_split(osv.osv_memory):
+ _inherit = "stock.move.split"
+ _name = "stock.inventory.line.split"
+ _description = "Split inventory lines"
+
+
+ def default_get(self, cr, uid, fields, context):
+ """
+ To check the availability of production lot.
+
+ @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 which we want default values
+ @param context: A standard dictionary
+
+ @return: A dictionary which of fields with values.
+
+ """
+ record_id = context and context.get('active_id',False)
+ res = super(stock_inventory_line_split, self).default_get(cr, uid, fields, context=context)
+ line= self.pool.get('stock.inventory.line').browse(cr, uid, record_id)
+ if 'product_id' in fields:
+ res.update({'product_id':line.product_id.id})
+ return res
+
+ def split(self, cr, uid, ids, line_ids, context=None):
+ """
+ To split stock inventory lines according to production lot
+
+ @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 line_ids: the ID or list of IDs of inventory lines we want to split
+ @param context: A standard dictionary
+
+ @return:
+
+ """
+ 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):
+ for inv_line in line_obj.browse(cr, uid, line_ids):
+ line_qty = inv_line.product_qty
+ quantity_rest = inv_line.product_qty
+ new_line = []
+ for line in data.line_ids:
+ 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,
+ }
+ current_line = line_obj.copy(cr, uid, inv_line.id, default_val)
+ new_line.append(current_line)
+ prodlot_id = False
+ if line.use_exist and line.name:
+ prodlot_id = prodlot_obj.search(cr, uid, [('prefix','=',line.name),('product_id','=',data.product_id.id)])
+ if prodlot_id:
+ prodlot_id = prodlot_id[0]
+ if not prodlot_id:
+ sequence = ir_sequence_obj.get(cr, uid, 'stock.lot.serial')
+ prodlot_id = prodlot_obj.create(cr, uid, {'name': sequence, 'prefix' : line.name},
+ {'product_id': data.product_id.id})
+ line_obj.write(cr, uid, [current_line], {'prod_lot_id': prodlot_id})
+ prodlot = prodlot_obj.browse(cr, uid, prodlot_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
+stock_inventory_line_split()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
diff --git a/addons/stock/wizard/stock_inventory_line_split_view.xml b/addons/stock/wizard/stock_inventory_line_split_view.xml
new file mode 100644
index 00000000000..42b8a4648da
--- /dev/null
+++ b/addons/stock/wizard/stock_inventory_line_split_view.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+ Split inventory lines
+ stock.inventory.line.split
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Split inventory lines
+ ir.actions.act_window
+ stock.inventory.line.split
+ form
+ form
+ new
+
+
+
+
diff --git a/addons/stock/wizard/stock_inventory_merge.py b/addons/stock/wizard/stock_inventory_merge.py
new file mode 100644
index 00000000000..64620054ff8
--- /dev/null
+++ b/addons/stock/wizard/stock_inventory_merge.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+from osv import fields, osv
+from service import web_services
+from tools.misc import UpdateableStr, UpdateableDict
+from tools.translate import _
+import netsvc
+import pooler
+import time
+import wizard
+
+class stock_inventory_merge(osv.osv_memory):
+ _name = "stock.inventory.merge"
+ _description = "Merge Inventory"
+ _columns = {
+
+ }
+
+ def do_merge(self, cr, uid, ids, context):
+
+ invent_obj = self.pool.get('stock.inventory')
+ invent_line_obj = self.pool.get('stock.inventory.line')
+
+ invent_lines = {}
+
+ if len(context['active_ids']) < 2:
+ raise osv.except_osv(_('Warning'),
+ _('Please select at least two inventories.'))
+
+
+
+ for inventory in invent_obj.browse(cr, uid, context['active_ids'], context=context):
+ print"-------active-ids----",context['active_ids']
+ 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)
+ 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': key[2],
+ 'product_qty': quantity,
+ })
+
+ return {}
+
+stock_inventory_merge()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
diff --git a/addons/stock/wizard/stock_inventory_set_stock_zero.py b/addons/stock/wizard/stock_inventory_set_stock_zero.py
new file mode 100644
index 00000000000..63296b69875
--- /dev/null
+++ b/addons/stock/wizard/stock_inventory_set_stock_zero.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+from osv import fields, osv
+from service import web_services
+from tools.misc import UpdateableStr, UpdateableDict
+from tools.translate import _
+import netsvc
+import pooler
+import time
+import wizard
+
+
+class inventory_set_stock_zero(osv.osv_memory):
+ _name = "stock.inventory.set.stock.zero"
+ _description = "Set Stock to 0"
+ _columns = {
+ 'location_id': fields.many2one('stock.location', 'Location', required=True),
+ }
+
+ def do_merge(self, cr, uid, ids, context):
+ """
+ To set stock to Zero
+
+ @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:
+
+ """
+ invent_obj = pooler.get_pool(cr.dbname).get('stock.inventory')
+ invent_line_obj = pooler.get_pool(cr.dbname).get('stock.inventory.line')
+ prod_obj = pooler.get_pool(cr.dbname).get('product.product')
+
+ if len(context['active_ids']) <> 1:
+ raise osv.except_osv(_('Warning'),
+ _('Please select one and only one inventory !'))
+ for id in ids:
+ datas = self.read(cr, uid, id)
+ loc = str(datas['location_id'])
+
+ cr.execute('select distinct location_id,product_id \
+ from stock_inventory_line \
+ where inventory_id=%s', (context['active_id'],))
+ inv = cr.fetchall()
+ cr.execute('select distinct product_id from stock_move where \
+ location_dest_id=%s or location_id=%s', (loc, loc,))
+ stock = cr.fetchall()
+ for s in stock:
+ if (loc, s[0]) not in inv:
+ p = prod_obj.browse(cr, uid, s[0])
+ invent_line_obj.create(cr, uid, {
+ 'inventory_id': context['active_id'],
+ 'location_id': loc,
+ 'product_id': s[0],
+ 'product_uom': p.uom_id.id,
+ 'product_qty': 0.0,
+ })
+ return {}
+
+inventory_set_stock_zero()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/stock/wizard/stock_inventory_set_stock_zero_view.xml b/addons/stock/wizard/stock_inventory_set_stock_zero_view.xml
new file mode 100644
index 00000000000..0ee84d2045d
--- /dev/null
+++ b/addons/stock/wizard/stock_inventory_set_stock_zero_view.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ Set Stock to Zero
+ stock.inventory.set.stock.zero
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/stock/wizard/stock_invoice_onshipping.py b/addons/stock/wizard/stock_invoice_onshipping.py
new file mode 100644
index 00000000000..22e48631f8b
--- /dev/null
+++ b/addons/stock/wizard/stock_invoice_onshipping.py
@@ -0,0 +1,135 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+from osv import fields, osv
+from service import web_services
+from tools.misc import UpdateableStr, UpdateableDict
+from tools.translate import _
+import netsvc
+import pooler
+import time
+import wizard
+
+class stock_invoice_onshipping(osv.osv_memory):
+ _name = "stock.invoice.onshipping"
+ _description = "Stock Invoice Onshipping"
+ _columns = {
+ 'journal_id': fields.many2one('account.journal', 'Destination Journal', required=True),
+ 'group': fields.boolean("Group by partner"),
+ 'type': fields.selection([('out_invoice', 'Customer Invoice'),
+ ('in_invoice', 'Supplier Invoice'),
+ ('out_refund', 'Customer Refund'),
+ ('in_refund', 'Supplier Refund')] , 'Type', required=True),
+ 'invoice_date': fields.date('Invoiced date'),
+ }
+
+ def _get_type(self, cr, uid, context):
+ """
+ To get invoice type
+
+ @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: invoice type
+
+ """
+ picking_obj = self.pool.get('stock.picking')
+ usage = 'customer'
+ pick = picking_obj.browse(cr, uid, context['active_id'])
+ if pick.invoice_state == 'invoiced':
+ raise osv.except_osv(_('UserError'), _('Invoice is already created.'))
+ if pick.invoice_state == 'none':
+ raise osv.except_osv(_('UserError'), _('Invoice cannot be created from Picking.'))
+ if pick.move_lines:
+ usage = pick.move_lines[0].location_id.usage
+
+ if pick.type == 'out' and usage == 'supplier':
+ type = 'in_refund'
+ elif pick.type == 'out' and usage == 'customer':
+ type = 'out_invoice'
+ elif pick.type == 'in' and usage == 'supplier':
+ type = 'in_invoice'
+ elif pick.type == 'in' and usage == 'customer':
+ type = 'out_refund'
+ else:
+ type = 'out_invoice'
+ return type
+
+ _defaults = {
+ 'type': _get_type,
+ }
+
+ def create_invoice(self, cr, uid, ids, context):
+ """
+ To create invoice
+
+ @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: invoice ids
+
+ """
+ result = []
+ picking_obj = self.pool.get('stock.picking')
+ mod_obj = self.pool.get('ir.model.data')
+ act_obj = self.pool.get('ir.actions.act_window')
+
+ for onshipdata_obj in self.read(cr, uid, ids, ['journal_id', 'group', 'type', 'invoice_date']):
+ if context.get('new_picking', False):
+ onshipdata_obj[id] = onshipdata_obj.new_picking
+ onshipdata_obj[ids] = onshipdata_obj.new_picking
+
+ type = onshipdata_obj['type']
+ context['date_inv'] = onshipdata_obj['invoice_date']
+ res = picking_obj.action_invoice_create(cr, uid,context['active_ids'],
+ journal_id = onshipdata_obj['journal_id'],
+ group=onshipdata_obj['group'],
+ type=type,
+ context=context)
+ invoice_ids = res.values()
+ if not invoice_ids:
+ raise osv.except_osv(_('Error'), _('Invoice is not created'))
+
+ if type == 'out_invoice':
+ xml_id = 'action_invoice_tree1'
+ elif type == 'in_invoice':
+ xml_id = 'action_invoice_tree2'
+ elif type == 'out_refund':
+ xml_id = 'action_invoice_tree3'
+ else:
+ xml_id = 'action_invoice_tree4'
+
+ result = mod_obj._get_id(cr, uid, 'account', xml_id)
+ id = mod_obj.read(cr, uid, result, ['res_id'])
+ result = act_obj.read(cr, uid, id['res_id'])
+ result['res_id'] = invoice_ids
+ return result
+
+stock_invoice_onshipping()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
diff --git a/addons/stock/wizard/stock_invoice_onshipping_view.xml b/addons/stock/wizard/stock_invoice_onshipping_view.xml
new file mode 100644
index 00000000000..62689019af0
--- /dev/null
+++ b/addons/stock/wizard/stock_invoice_onshipping_view.xml
@@ -0,0 +1,33 @@
+
+
+
+
+ Stock Invoice Onshipping
+ stock.invoice.onshipping
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/stock/wizard/stock_location_product.py b/addons/stock/wizard/stock_location_product.py
new file mode 100755
index 00000000000..d3ee334bac0
--- /dev/null
+++ b/addons/stock/wizard/stock_location_product.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+from osv import fields, osv
+
+class stock_location_product(osv.osv_memory):
+ _name = "stock.location.product"
+ _description = "Products by Location"
+ _columns = {
+ 'from_date': fields.datetime('From'),
+ 'to_date': fields.datetime('To'),
+ }
+
+ def action_open_window(self, cr, uid, ids, context):
+ """
+ To open location wise product information specific to given duration
+
+ @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: invoice type
+
+ """
+ mod_obj = self.pool.get('ir.model.data')
+ for location_obj in self.read(cr, uid, ids, ['from_date', 'to_date']):
+ return {
+ 'name': False,
+ 'view_type': 'form',
+ 'view_mode': 'tree,form',
+ 'res_model': 'product.product',
+ 'type': 'ir.actions.act_window',
+ 'context': {'location': context['active_id'],
+ 'from_date': location_obj['from_date'],
+ 'to_date': location_obj['to_date']},
+ 'domain': [('type', '<>', 'service')],
+ }
+
+stock_location_product()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/stock/wizard/stock_location_product_view.xml b/addons/stock/wizard/stock_location_product_view.xml
new file mode 100644
index 00000000000..518bc6eab35
--- /dev/null
+++ b/addons/stock/wizard/stock_location_product_view.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ Products by Location
+ stock.location.product
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/stock/stock_wizard.py b/addons/stock/wizard/stock_move.py
similarity index 65%
rename from addons/stock/stock_wizard.py
rename to addons/stock/wizard/stock_move.py
index eb522c2e2fe..500b0bdc372 100644
--- a/addons/stock/stock_wizard.py
+++ b/addons/stock/wizard/stock_move.py
@@ -36,6 +36,18 @@ class stock_move_track(osv.osv_memory):
}
def track_lines(self, cr, uid, ids, context={}):
+ """
+ To track stock moves lines
+
+ @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:
+
+ """
datas = self.read(cr, uid, ids)[0]
move_obj = self.pool.get('stock.move')
move_obj._track_lines(cr, uid, context['active_id'], datas, context=context)
@@ -52,32 +64,46 @@ class stock_move_consume(osv.osv_memory):
'product_qty': fields.float('Quantity', required=True),
'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
'location_id': fields.many2one('stock.location', 'Location', required=True)
- }
+ }
- def _get_product_id(self, cr, uid, context):
+ 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
+
+ """
+ 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)
- return move.product_id.id
-
- def _get_product_qty(self, cr, uid, context):
- move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context)
- return move.product_qty
-
- def _get_product_uom(self, cr, uid, context):
- move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context)
- return move.product_uom.id
-
- def _get_location_id(self, cr, uid, context):
- move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context)
- return move.location_id.id
-
- _defaults = {
- 'product_id': _get_product_id,
- 'product_qty': _get_product_qty,
- 'product_uom': _get_product_uom,
- 'location_id': _get_location_id
- }
-
+ 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={}):
+ """
+ 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:
+
+ """
move_obj = self.pool.get('stock.move')
move_ids = context['active_ids']
for data in self.read(cr, uid, ids):
@@ -99,6 +125,18 @@ class stock_move_scrap(osv.osv_memory):
}
def move_scrap(self, cr, uid, ids, context={}):
+ """
+ To move scraped 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:
+
+ """
move_obj = self.pool.get('stock.move')
move_ids = context['active_ids']
for data in self.read(cr, uid, ids):
@@ -114,24 +152,60 @@ class split_in_production_lot(osv.osv_memory):
_name = "stock.move.split"
_description = "Split in Production lots"
+ 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
+
+ """
+
+ res = super(split_in_production_lot, 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})
+ return res
+
_columns = {
'product_id': fields.many2one('product.product', 'Product', required=True, select=True),
'line_ids': fields.one2many('stock.move.split.lines', 'lot_id', 'Lots Number')
}
-
- def _get_product_id(self, cr, uid, context):
- move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context)
- return move.product_id.id
-
- _defaults = {
- 'product_id': _get_product_id,
- }
-
def split_lot(self, cr, uid, ids, context=None):
+ """
+ To split a lot
+
+ @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:
+
+ """
self.split(cr, uid, ids, context.get('active_ids'), context=context)
return {}
def split(self, cr, uid, ids, move_ids, context=None):
+ """
+ To split stock moves into production lot
+
+ @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 move_ids: the ID or list of IDs of stock move we want to split
+ @param context: A standard dictionary
+
+ @return:
+
+ """
prodlot_obj = self.pool.get('stock.production.lot')
ir_sequence_obj = self.pool.get('ir.sequence')
move_obj = self.pool.get('stock.move')
@@ -182,8 +256,6 @@ class split_in_production_lot(osv.osv_memory):
update_val['product_qty'] = quantity_rest
update_val['product_uos_qty'] = uos_qty_rest
move_obj.write(cr, uid, [move.id], update_val)
-
-
return new_move
split_in_production_lot()
diff --git a/addons/stock/stock_wizard_view.xml b/addons/stock/wizard/stock_move_view.xml
similarity index 100%
rename from addons/stock/stock_wizard_view.xml
rename to addons/stock/wizard/stock_move_view.xml
diff --git a/addons/stock/wizard/stock_partial_move.py b/addons/stock/wizard/stock_partial_move.py
new file mode 100644
index 00000000000..bf8d1058aa2
--- /dev/null
+++ b/addons/stock/wizard/stock_partial_move.py
@@ -0,0 +1,217 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+from osv import fields, osv
+from service import web_services
+from tools.translate import _
+import netsvc
+import pooler
+import time
+
+class stock_partial_move(osv.osv_memory):
+ _name = "stock.partial.move"
+ _description = "Partial Move"
+ _columns = {
+ 'date': fields.datetime('Date', required=True),
+ 'partner_id': fields.many2one('res.partner',string="Partner", required=True),
+ 'address_id': fields.many2one('res.partner.address', 'Delivery Address', help="Address where goods are to be delivered", required=True),
+
+ }
+
+ def view_init(self, cr, uid, fields_list, context=None):
+ res = super(stock_partial_move, self).view_init(cr, uid, fields_list, context=context)
+ move_obj = self.pool.get('stock.move')
+ if not context:
+ context={}
+ moveids = []
+ for m in move_obj.browse(cr, uid, context.get('active_ids', [])):
+ if m.state in ('done', 'cancel'):
+ continue
+ if 'move%s_product_id'%(m.id) not in self._columns:
+ self._columns['move%s_product_id'%(m.id)] = fields.many2one('product.product',string="Product")
+ if 'move%s_product_qty'%(m.id) not in self._columns:
+ self._columns['move%s_product_qty'%(m.id)] = fields.float("Quantity")
+ if 'move%s_product_uom'%(m.id) not in self._columns:
+ self._columns['move%s_product_uom'%(m.id)] = fields.many2one('product.uom',string="Product UOM")
+
+ if (m.picking_id.type == 'in') and (m.product_id.cost_method == 'average'):
+ if 'move%s_product_price'%(m.id) not in self._columns:
+ self._columns['move%s_product_price'%(m.id)] = fields.float("Price")
+ if 'move%s_product_currency'%(m.id) not in self._columns:
+ self._columns['move%s_product_currency'%(m.id)] = fields.many2one('res.currency',string="Currency")
+ return res
+
+ def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False,submenu=False):
+ result = super(stock_partial_move, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar,submenu)
+ move_obj = self.pool.get('stock.move')
+ move_ids = context.get('active_ids', False)
+ move_ids = move_obj.search(cr, uid, [('id','in',move_ids)])
+ _moves_arch_lst = """
"""
+ result['arch'] = _moves_arch_lst
+ result['fields'] = _moves_fields
+ return result
+
+ def default_get(self, cr, uid, fields, context=None):
+ """
+ To get default values for the object.
+
+ @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 which we want default values
+ @param context: A standard dictionary
+
+ @return: A dictionary which of fields with values.
+
+ """
+
+ res = super(stock_partial_move, self).default_get(cr, uid, fields, context=context)
+ move_obj = self.pool.get('stock.move')
+ if not context:
+ context={}
+ moveids = []
+ if 'date' in fields:
+ res.update({'date': time.strftime('%Y-%m-%d %H:%M:%S')})
+ for m in move_obj.browse(cr, uid, context.get('active_ids', [])):
+ if m.state in ('done', 'cancel'):
+ continue
+ if 'move%s_product_id'%(m.id) in fields:
+ res['move%s_product_id'%(m.id)] = m.product_id.id
+ if 'move%s_product_qty'%(m.id) in fields:
+ res['move%s_product_qty'%(m.id)] = m.product_qty
+ if 'move%s_product_uom'%(m.id) in fields:
+ res['move%s_product_uom'%(m.id)] = m.product_uom.id
+
+ if (m.picking_id.type == 'in') and (m.product_id.cost_method == 'average'):
+ price = 0
+ if hasattr(m, 'purchase_line_id') and m.purchase_line_id:
+ price = m.purchase_line_id.price_unit
+
+ currency = False
+ if hasattr(m.picking_id, 'purchase_id') and m.picking_id.purchase_id:
+ currency = m.picking_id.purchase_id.pricelist_id.currency_id.id
+
+ if 'move%s_product_price'%(m.id) in fields:
+ res['move%s_product_price'%(m.id)] = price
+ if 'move%s_product_currency'%(m.id) in fields:
+ res['move%s_product_currency'%(m.id)] = currency
+ return res
+
+ def do_partial(self, cr, uid, ids, context):
+ move_obj = self.pool.get('stock.move')
+ move_ids = context.get('active_ids', False)
+ partial = self.browse(cr, uid, ids[0], context)
+ partial_datas = {
+ 'partner_id' : partial.partner_id and partial.partner_id.id or False,
+ 'address_id' : partial.address_id and partial.address_id.id or False,
+ 'delivery_date' : partial.date
+ }
+ for m in move_obj.browse(cr, uid, move_ids):
+ if m.state in ('done', 'cancel'):
+ continue
+ partial_datas['move%s'%(m.id)] = {
+ 'product_id' : getattr(partial, 'move%s_product_id'%(m.id)).id,
+ 'product_qty' : getattr(partial, 'move%s_product_qty'%(m.id)),
+ 'product_uom' : getattr(partial, 'move%s_product_uom'%(m.id)).id
+ }
+
+ if (m.picking_id.type == 'in') and (m.product_id.cost_method == 'average'):
+ partial_datas['move%s'%(m.id)].update({
+ 'product_price' : getattr(partial, 'move%s_product_price'%(m.id)),
+ 'product_currency': getattr(partial, 'move%s_product_currency'%(m.id)).id
+ })
+
+ res = move_obj.do_partial(cr, uid, move_ids, partial_datas, context=context)
+ return {}
+
+stock_partial_move()
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
diff --git a/addons/stock/wizard/stock_partial_move_view.xml b/addons/stock/wizard/stock_partial_move_view.xml
new file mode 100644
index 00000000000..30b7f3fca0e
--- /dev/null
+++ b/addons/stock/wizard/stock_partial_move_view.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Delivery Product
+ stock.partial.move
+ form
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/stock/wizard/stock_partial_picking.py b/addons/stock/wizard/stock_partial_picking.py
new file mode 100644
index 00000000000..eba095718cd
--- /dev/null
+++ b/addons/stock/wizard/stock_partial_picking.py
@@ -0,0 +1,235 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+from osv import fields, osv
+from service import web_services
+from tools.translate import _
+import netsvc
+import pooler
+import time
+
+class stock_partial_picking(osv.osv_memory):
+ _name = "stock.partial.picking"
+ _description = "Partial Picking"
+ _columns = {
+ 'date': fields.datetime('Date', required=True),
+ 'partner_id': fields.many2one('res.partner',string="Partner", required=True),
+ 'address_id': fields.many2one('res.partner.address', 'Delivery Address', help="Address where goods are to be delivered", required=True),
+
+ }
+
+ def view_init(self, cr, uid, fields_list, context=None):
+ res = super(stock_partial_picking, self).view_init(cr, uid, fields_list, context=context)
+ pick_obj = self.pool.get('stock.picking')
+ if not context:
+ context={}
+ moveids = []
+ for pick in pick_obj.browse(cr, uid, context.get('active_ids', [])):
+ for m in pick.move_lines:
+ if m.state in ('done', 'cancel'):
+ continue
+ if 'move%s_product_id'%(m.id) not in self._columns:
+ self._columns['move%s_product_id'%(m.id)] = fields.many2one('product.product',string="Product")
+ if 'move%s_product_qty'%(m.id) not in self._columns:
+ self._columns['move%s_product_qty'%(m.id)] = fields.float("Quantity")
+ if 'move%s_product_uom'%(m.id) not in self._columns:
+ self._columns['move%s_product_uom'%(m.id)] = fields.many2one('product.uom',string="Product UOM")
+
+ if (pick.type == 'in') and (m.product_id.cost_method == 'average'):
+ if 'move%s_product_price'%(m.id) not in self._columns:
+ self._columns['move%s_product_price'%(m.id)] = fields.float("Price")
+ if 'move%s_product_currency'%(m.id) not in self._columns:
+ self._columns['move%s_product_currency'%(m.id)] = fields.many2one('res.currency',string="Currency")
+ return res
+
+ def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False,submenu=False):
+ result = super(stock_partial_picking, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar,submenu)
+ pick_obj = self.pool.get('stock.picking')
+ picking_ids = context.get('active_ids', False)
+ picking_ids = pick_obj.search(cr, uid, [('id', 'in', picking_ids)])
+ _moves_arch_lst = """
+
+
+
+
+
+
+
+
+ Stock traceability lot upstream
+ stock.traceability.lot.upstream
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Stock traceability lot downstream
+ stock.traceability.lot.downstream
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/stock/wizard/wizard_inventory.py b/addons/stock/wizard/wizard_inventory.py
deleted file mode 100644
index 42345353866..00000000000
--- a/addons/stock/wizard/wizard_inventory.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# 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 .
-#
-##############################################################################
-
-import time
-import wizard
-import ir
-import pooler
-from osv.osv import except_osv
-from osv import fields,osv
-import netsvc
-from tools.translate import _
-
-inventory_form = """
-
-
-
-
-
-
-
-
-"""
-
-inventory_fields = {
- 'location_id' : {
- 'string':'Location',
- 'type':'many2one',
- 'relation':'stock.location',
- 'required':True
- },
- 'recursive' : {'string':'Include all childs for the location', 'type':'boolean'}
-}
-
-
-
-def _fill_inventory(self, cr, uid, data, context):
- pool = pooler.get_pool(cr.dbname)
- inventory_line_obj = pooler.get_pool(cr.dbname).get('stock.inventory.line')
- location_obj = pooler.get_pool(cr.dbname).get('stock.location')
- res={}
- res_location={}
- if data['form']['recursive'] :
- location_ids = location_obj.search(cr, uid, [('location_id', 'child_of', [data['form']['location_id']])])
- for location in location_ids :
- res=location_obj._product_get(cr, uid, location)
- res_location[location]=res
- else:
- context.update({'compute_child':False})
- res=location_obj._product_get(cr, uid, data['form']['location_id'],context=context)
- res_location[data['form']['location_id']]=res
-
- product_ids=[]
- for location in res_location.keys():
- res=res_location[location]
- for product_id in res.keys():
- #product_ids.append(product_id)
- prod = pool.get('product.product').browse(cr, uid, [product_id])[0]
- uom = prod.uom_id.id
- context.update({'uom': uom})
- amount=pool.get('stock.location')._product_get(cr, uid, location, [product_id], context=context)[product_id]
-
- if(amount):
- line_ids=inventory_line_obj.search(cr,uid,[('inventory_id','=',data['id']),('location_id','=',location),('product_id','=',product_id),('product_uom','=',uom),('product_qty','=',amount)])
- if not len(line_ids):
- inventory_line={'inventory_id':data['id'],'location_id':location,'product_id':product_id,'product_uom':uom,'product_qty':amount}
- inventory_line_obj.create(cr, uid, inventory_line)
- product_ids.append(product_id)
-
- if(len(product_ids)==0):
- raise wizard.except_wizard(_('Message !'), _('No product in this location.'))
- return {}
-
-
-
-class fill_inventory(wizard.interface):
- states = {
- 'init' : {
- 'actions' : [],
- 'result' : {'type' : 'form',
- 'arch' : inventory_form,
- 'fields' : inventory_fields,
- 'state' : [('end', 'Cancel', 'gtk-cancel'),('fill_inventory', 'Fill Inventory', 'gtk-apply', True) ]}
- },
- 'fill_inventory' : {
- 'actions' : [],
- 'result' : {'type' : 'action', 'action': _fill_inventory, 'state' : 'end'}
- },
- }
-fill_inventory("stock.fill_inventory")
-
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/stock/wizard/wizard_invoice_onshipping.py b/addons/stock/wizard/wizard_invoice_onshipping.py
deleted file mode 100644
index 22cdbc97e70..00000000000
--- a/addons/stock/wizard/wizard_invoice_onshipping.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# 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 .
-#
-##############################################################################
-
-import wizard
-import pooler
-from tools.translate import _
-
-invoice_form = """
-
-
-
-
-
-
-
-
-
-
-"""
-
-invoice_fields = {
- 'journal_id': {
- 'string': 'Destination Journal',
- 'type': 'many2one',
- 'relation': 'account.journal',
- 'required': True
- },
- 'group': {
- 'string': 'Group by partner',
- 'type': 'boolean'
- },
- 'type': {
- 'string': 'Type',
- 'type': 'selection',
- 'selection': [],
- 'required': True
- },
- 'invoice_date': {'string': 'Invoiced date', 'type':'date' }
-}
-
-
-def _get_type(obj, cr, uid, data, context):
- picking_obj = pooler.get_pool(cr.dbname).get('stock.picking')
- usage = 'customer'
- pick = picking_obj.browse(cr, uid, data['id'], context)
- if pick.invoice_state == 'invoiced':
- raise wizard.except_wizard(_('UserError'), _('Invoice is already created.'))
- if pick.invoice_state == 'none':
- raise wizard.except_wizard(_('UserError'), _('Invoice cannot be created from Picking.'))
-
- if pick.move_lines:
- usage = pick.move_lines[0].location_id.usage
-
- if pick.type =='out':
- invoice_fields['type']['selection'] = [
- ('out_invoice', 'Customer Invoice'),
- ('in_refund', 'Supplier Refund'),
- ]
- elif pick.type =='in':
- invoice_fields['type']['selection'] = [
- ('in_invoice', 'Supplier Invoice'),
- ('out_refund', 'Customer Refund'),
- ]
- else:
- invoice_fields['type']['selection']=[
- ('out_invoice', 'Customer Invoice'),
- ('in_invoice', 'Supplier Invoice'),
- ('out_refund', 'Customer Refund'),
- ('in_refund', 'Supplier Refund'),
- ]
-
- if pick.type == 'out' and usage == 'supplier':
- type = 'in_refund'
- elif pick.type == 'out' and usage == 'customer':
- type = 'out_invoice'
- elif pick.type == 'in' and usage == 'supplier':
- type = 'in_invoice'
- elif pick.type == 'in' and usage == 'customer':
- type = 'out_refund'
- else:
- type = 'out_invoice'
- return {'type': type}
-
-
-def _create_invoice(obj, cr, uid, data, context):
- if data['form'].get('new_picking', False):
- data['id'] = data['form']['new_picking']
- data['ids'] = [data['form']['new_picking']]
- pool = pooler.get_pool(cr.dbname)
- picking_obj = pooler.get_pool(cr.dbname).get('stock.picking')
- mod_obj = pool.get('ir.model.data')
- act_obj = pool.get('ir.actions.act_window')
-
- type = data['form']['type']
-
- context['date_inv'] = data['form']['invoice_date']
- res = picking_obj.action_invoice_create(cr, uid, data['ids'],
- journal_id = data['form']['journal_id'], group=data['form']['group'],
- type=type, context= context)
-
- invoice_ids = res.values()
- if not invoice_ids:
- raise wizard.except_wizard(_('Error'), _('Invoice is not created'))
-
- if type == 'out_invoice':
- xml_id = 'action_invoice_tree1'
- elif type == 'in_invoice':
- xml_id = 'action_invoice_tree2'
- elif type == 'out_refund':
- xml_id = 'action_invoice_tree3'
- else:
- xml_id = 'action_invoice_tree4'
-
- result = mod_obj._get_id(cr, uid, 'account', xml_id)
- id = mod_obj.read(cr, uid, result, ['res_id'])
- result = act_obj.read(cr, uid, id['res_id'])
- result['res_id'] = invoice_ids
- return result
-
-
-class make_invoice_onshipping(wizard.interface):
- states = {
- 'init': {
- 'actions': [_get_type],
- 'result': {
- 'type': 'form',
- 'arch': invoice_form,
- 'fields': invoice_fields,
- 'state': [
- ('end', 'Cancel', 'gtk-cancel'),
- ('create_invoice', 'Create invoice', 'gtk-apply', True)
- ]
- }
- },
- 'create_invoice': {
- 'actions': [],
- 'result': {
- 'type': 'action',
- 'action': _create_invoice,
- 'state': 'end'
- }
- },
- }
-
-make_invoice_onshipping("stock.invoice_onshipping")
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/addons/stock/wizard/wizard_location_product.py b/addons/stock/wizard/wizard_location_product.py
deleted file mode 100755
index c423c426832..00000000000
--- a/addons/stock/wizard/wizard_location_product.py
+++ /dev/null
@@ -1,76 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# 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 .
-#
-##############################################################################
-
-import wizard
-import pooler
-import time
-
-def _action_open_window(self, cr, uid, data, context):
- pool = pooler.get_pool(cr.dbname)
- mod_obj = pool.get('ir.model.data')
- result = mod_obj._get_id(cr, uid, 'product', 'product_search_form_view')
- id = mod_obj.read(cr, uid, result, ['res_id'])
- return {
- 'name': False,
- 'view_type': 'form',
- "view_mode": 'tree,form',
- 'res_model': 'product.product',
- 'type': 'ir.actions.act_window',
- 'context':{'location': data['ids'][0],'from_date':data['form']['from_date'],'to_date':data['form']['to_date']},
- 'domain':[('type','<>','service')],
- 'search_view_id': id['res_id']
- }
-
-
-class product_by_location(wizard.interface):
- form1 = '''
-
-
-
-
-
-
-
-
-
'''
- form1_fields = {
- 'from_date': {
- 'string': 'From',
- 'type': 'datetime',
- },
- 'to_date': {
- 'string': 'To',
- 'type': 'datetime',
- },
- }
-
- states = {
- 'init': {
- 'actions': [],
- 'result': {'type': 'form', 'arch':form1, 'fields':form1_fields, 'state': [('end', 'Cancel', 'gtk-cancel'),('open', 'Open Products', 'gtk-apply', True)]}
- },
- 'open': {
- 'actions': [],
- 'result': {'type': 'action', 'action': _action_open_window, 'state':'end'}
- }
- }
-product_by_location('stock.location.products')
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/stock/wizard/wizard_partial_move.py b/addons/stock/wizard/wizard_partial_move.py
deleted file mode 100644
index 928d96461c8..00000000000
--- a/addons/stock/wizard/wizard_partial_move.py
+++ /dev/null
@@ -1,220 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# 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 .
-#
-##############################################################################
-
-import time
-import netsvc
-from tools.misc import UpdateableStr, UpdateableDict
-import pooler
-
-import wizard
-from osv import osv
-import tools
-from tools.translate import _
-
-_moves_arch = UpdateableStr()
-_moves_fields = UpdateableDict()
-
-_moves_arch_end = '''
-
-'''
-inv_fields = {
- 'prefix': {
- 'string': 'Prefix',
- 'type': 'char',
- 'size': 64,
- },
- 'quantity': {
- 'string': 'Quantity per lot',
- 'type': 'float',
- 'default': 1,
- }
-}
-
-def _check_production_lot(self, cr, uid, data, context):
- pool = pooler.get_pool(cr.dbname)
- inv_obj = pool.get('stock.inventory.line').browse(cr, uid, data['id'])
- if not inv_obj.prod_lot_id:
- raise wizard.except_wizard(_('Caution!'), _('Before splitting the inventory lines, make sure the production lot is assigned to this product.'))
- return data['form']
-
-def _split_lines(self, cr, uid, data, context):
- inv_id = data['id']
-
- pool = pooler.get_pool(cr.dbname)
- inv_line_obj = pool.get('stock.inventory.line')
- prodlot_obj = pool.get('stock.production.lot')
-
- ir_sequence_obj = pool.get('ir.sequence')
-
- sequence = ir_sequence_obj.get(cr, uid, 'stock.lot.serial')
- if not sequence:
- raise wizard.except_wizard(_('Error!'), _('No production sequence defined'))
- if data['form']['prefix']:
- sequence=data['form']['prefix']+'/'+(sequence or '')
-
- inv = inv_line_obj.browse(cr, uid, [inv_id])[0]
- quantity=data['form']['quantity']
- prodlot_obj.write(cr, uid, inv.prod_lot_id.id, {'name':sequence})
-
- if quantity <= 0 or inv.product_qty == 0:
- return {}
-
- quantity_rest = inv.product_qty%quantity
-
- update_val = {
- 'product_qty': quantity,
- }
-
- new_line = []
- for idx in range(int(inv.product_qty//quantity)):
- if idx:
- current_line = inv_line_obj.copy(cr, uid, inv.id, {'prod_lot_id': inv.prod_lot_id.id})
- new_line.append(current_line)
- else:
- current_line = inv.id
- inv_line_obj.write(cr, uid, [current_line], update_val)
-
- if quantity_rest > 0:
- idx = int(inv.product_qty//quantity)
- update_val['product_qty']=quantity_rest
-
- if idx:
- current_line = inv_line_obj.copy(cr, uid, inv.id, {'prod_lot_id': inv.prod_lot_id.id})
- new_line.append(current_line)
- else:
- current_line = inv.id
- inv_line_obj.write(cr, uid, [current_line], update_val)
-
- return {}
-
-class wizard_split_inventory_lots(wizard.interface):
- states = {
- 'init': {
- 'actions': [_check_production_lot],
- 'result': {'type': 'form', 'arch': inv_form, 'fields': inv_fields, 'state': [('end', 'Cancel', 'gtk-cancel'), ('split', 'Ok', 'gtk-apply', True)]},
- },
- 'split': {
- 'actions': [_split_lines],
- 'result': {'type':'state', 'state':'end'}
- }
- }
-
-wizard_split_inventory_lots('stock.inventory.line.split')
\ No newline at end of file
diff --git a/addons/stock_invoice_directly/i18n/pt_BR.po b/addons/stock_invoice_directly/i18n/pt_BR.po
index 82da1f47308..34ca6b82f62 100644
--- a/addons/stock_invoice_directly/i18n/pt_BR.po
+++ b/addons/stock_invoice_directly/i18n/pt_BR.po
@@ -13,7 +13,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2010-03-24 04:53+0000\n"
+"X-Launchpad-Export-Date: 2010-03-27 05:00+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#. module: stock_invoice_directly
diff --git a/addons/stock_planning/__init__.py b/addons/stock_planning/__init__.py
new file mode 100644
index 00000000000..f0fea471a21
--- /dev/null
+++ b/addons/stock_planning/__init__.py
@@ -0,0 +1,24 @@
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2009 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+import stock_planning
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
diff --git a/addons/stock_planning/__terp__.py b/addons/stock_planning/__terp__.py
new file mode 100644
index 00000000000..1ff4e8d0b10
--- /dev/null
+++ b/addons/stock_planning/__terp__.py
@@ -0,0 +1,146 @@
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2009 Tiny SPRL ().
+#
+# 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 .
+#
+##############################################################################
+
+{
+ "name":"Master Procurement Schedule",
+ "version":"1.1",
+ "author":"Tiny and Grzegorz Grzelak (Cirrus)",
+ "category":"Custom",
+ "depends":["hr","stock","sale"],
+ "description": """
+This module is based on original Tiny module stock_planning version 1.0 of the same name Master Procurement Schedule.
+
+Purpose of MPS is to allow create a manual procurement (requisition) apart of MRP scheduler (which works automatically on minimum stock rules).
+
+Terms used in the module:
+- Stock and Sales Period - is the time (between Start Date and End Date) for which you plan Stock and Sales Forecast and make Procurement Planning.
+- Stock and Sales Forecast - is the quantity of products you plan to sell in the Period.
+- Stock Planning - is the quantity of products you plan to purchase or produce for the Period.
+
+Because we have another module sale_forecast which uses terms "Sales Forecast" and "Planning" as amount values we will use terms "Stock and Sales Forecast" and "Stock Planning" to emphasize that we use quantity values.
+
+Activity with this module is divided to three steps:
+- Creating Periods. Mandatory step.
+- Creating Sale Forecasts and entering quantities to them. Optional step but useful for farther planning.
+- Creating Planning lines, entering quantities to them and making Procurement. Making procurement is the final step for the Period.
+
+Periods
+=======
+You have two menu items for Periods in "Sales Management - Configuration". There are:
+- "Create Sales Periods" - Which automates creating daily, weekly or monthly periods.
+- "Stock and sales Periods" - Which allows to create any type of periods, change the dates and change the State of period.
+
+Creating periods is the first step you have to do to use modules features. You can create custom periods using "New" button in "Stock and Sales Periods" form or view but it is recommended to use automating tool.
+
+Remarks:
+- These periods (officially Stock and Sales Periods) are separated of Financial or other periods in the system.
+- Periods are not assigned to companies (when you use multicompany feature at all). Module suppose that you use the same periods across companies. If you wish to use different periods for different companies define them as you wish (they can overlap). Later on in this text will be indications how to use such periods.
+- When periods are created automatically their start and finish dates are with with start hour 00:00:00 and end hour 23:59:00. Fe. when you create daily periods they will have start date 31.01.2010 00:00:00 and end date 31.01.2010 23:59:00. It works only in automatic creation of periods. When you create periods manually you have to take care about hours because you can have incorrect values form sales or stock.
+- If you use overlapping periods for the same product, warehouse and company results can be unpredictable.
+- If current date doesn't belong to any period or you have holes between periods results can be unpredictable.
+
+Sales Forecasts
+===============
+You have few menus for Sales forecast in "Sales Management - Sales Forecasts".
+- "Create Sales Forecasts for Sales Periods" - which automates creating forecasts lines according to some parameters.
+- "Sales Forecasts" - few menus for working with forecasts lists and forms.
+
+Menu "Create Sales Forecasts for Sales Periods" creates Forecasts for products from selected Category, for selected Period and for selected Warehouse. It is an option "Copy Last Forecast" to copy forecast and other settings of period before this one to created one.
+
+Remarks:
+- This tool doesn't create lines if relevant lines (for the same Product, Period, Warehouse and validated or created by you) already exists. If you wish to create another forecast if relevant lines exists you have to do it manually using menus described bellow.
+- When created lines are validated by someone else you can use this tool to create another lines for the same Period, Product and Warehouse.
+- When you choose "Copy Last Forecast" created line takes quantity and some settings from your (validated by you or created by you if not validated yet) forecast which is for last period before period of created forecast. If there are few your forecasts for period before this one (it is possible) system takes one of them (no rule which of them).
+
+
+Menus "Sales Forecasts"
+On "Sales Forecast" form mainly you have to enter a forecast quantity in "Product Quantity". Farther calculation can work for draft forecasts. But validation can save your data against any accidental changes. You can click "Validate" button but it is not mandatory.
+
+Instead of forecast quantity you can enter amount of forecast sales in field "Product Amount". System will count quantity from amount according to Sale price of the Product.
+
+All values on the form are expressed in unit of measure selected on form. You can select one of unit of measure from default category or from second category. When you change unit of measure the quanities will be recalculated according to new UoM: editable values (blue fields) immediately, non edited fields after clicking of "Calculate Planning" button.
+
+To find proper value for Sale Forecast you can use "Sales History" table for this product. You have to enter parameters to the top and left of this table and system will count sale quantities according to these parameters. So you can select fe. your department (at the top) then (to the left): last period, period before last and period year ago.
+
+Remarks:
+
+
+Procurement Planning
+====================
+Menu for Planning you can find in "Warehouse - Stock Planning".
+- "Create Stock Planning Lines" - allows you to automate creating planning lines according to some parameters.
+- "Master Procurement Scheduler" - is the most important menu of the module which allows to create procurement.
+
+As Sales forecast is phase of planning sales. The Procurement Planning (Planning) is the phase of scheduling Purchasing or Producing. You can create Procurement Planning quickly using tool from menu "Create Stock Planning Lines", then you can review created planning and make procurement using menu "Master Procurement Schedule".
+
+Menu "Create Stock Planning Lines" allows you to create quickly Planning lines for products from selected Category, for selected Period, and for selected Warehouse. When you check option "All Products with Forecast" system creates lines for all products having forecast for selected Period and Warehouse. Selected Category will be ignored in this case.
+
+Under menu "Master Procurement Scheduler" you can generally change the values "Planned Out" and "Planned In" to observe the field "Stock Simulation" and decide if this value would be accurate for end of the Period.
+"Planned Out" can be based on "Warehouse Forecast" which is the sum of all forecasts for Period and Warehouse. But your planning can be based on any other information you have. It is not necessary to have any forecast.
+"Planned In" quantity is used to calculate field "Incoming Left" which is the quantity to be procured to make stock as indicated in "Stock Simulation" at the and of Period. You can compare "Stock Simulation" quantity to minimum stock rules visible on the form. But you can plan different quantity than in Minimum Stock Rules. Calculation is made for whole Warehouse by default. But if you want to see values for Stock location of calculated warehouse you can use check box "Stock Location Only".
+
+If after few tries you decide that you found correct quantities for "Planned Out" and "Planned In" and you are satisfied with end of period stock calculated in "Stock Simulation" you can click "Procure Incoming Left" button to procure quantity of field "Incoming Left" into the Warehouse. System creates appropriate Procurement Order. You can decide if procurement will be made to Stock or Input location of calculated Warehouse.
+
+If you don't want to Produce or Buy the product but just pick calculated quantity from another warehouse you can click "Supply from Another Warehouse" (instead of "Procure Incoming Left"). System creates pick list with move from selected source Warehouse to calculated Warehouse (as destination). You can also decide if this pick should be done from Stock or Output location of source warehouse. Destination location (Stock or Input) of destination warehouse will be used as set for "Procure Incoming Left".
+
+To see proper quantities in fields "Confirmed In", "Confirmed Out", "Confirmed In Before", "Planned Out Before" and "Stock Simulation" you have to click button "Calculate Planning".
+
+All values on the form are expressed in unit of measure selected on form. You can select one of unit of measure from default category or from second category. When you change unit of measure the quanities will be recalculated according to new UoM: editable values (blue fields) immediately, non edited fields after clicking of "Calculate Planning" button.
+
+How Stock Simulation field is calculated:
+Generally Stock Simulation shows the stock for end of the calculated period according to some planned or confirmed stock moves. Calculation always starts with quantity of real stock of beginning of current period. Then calculation adds or subtracts quantities of calculated period or periods before calculated.
+When you are in the same period (current period is the same as calculated) Stock Simulation is calculated as follows:
+Stock Simulation =
+ Stock of beginning of current Period
+ - Planned Out
+ + Planned In
+
+When you calculate period next to current:
+Stock Simulation =
+ Stock of beginning of current Period
+ - Planned Out of current Period
+ + Confirmed In of current Period (incl. Already In)
+ - Planned Out of calculated Period
+ + Planned In of calculated Period .
+
+As you see calculated Period is taken the same way like in case above. But calculation of current Period are made a little bit different. First you should note that system takes for current Period only Confirmed In moves. It means that you have to make planning and procurement for current Period before this calculation (for Period next to current).
+
+When you calculate Period ahead:
+Stock Simulation =
+ Stock of beginning of current Period
+ - Sum of Planned Out of Periods before calculated
+ + Sum of Confirmed In of Periods before calculated (incl. Already In)
+ - Planned Out of calculated Period
+ + Planned In of calculated Period.
+
+Periods before calculated means periods starting from current till period before calculated.
+
+Remarks:
+- Remember to make planning for all periods before calculated because omitting these quantities and procurements can cause wrong suggestions for procurements few periods ahead.
+- If you made planning few periods ahead and you find that real Confirmed Out is bigger than Planned Out in some periods before you can repeat Planning and make another procurement. You should do it in the same planning line. If you create another planning line the suggestions can be wrong.
+- When you wish to work with different periods for some part of products define two kinds of periods (fe. Weekly and Monthly) and use them for different products. Example: If you use always Weekly periods for Products A, and Monthly periods for Products B your all calculations will work correctly. You can also use different kind of periods for the same products from different warehouse or companies. But you cannot use overlapping periods for the same product, warehouse and company because results can be unpredictable. The same apply to Forecasts lines.
+""",
+ "demo_xml":[],
+ "update_xml":["security/ir.model.access.csv","stock_planning_view.xml"],
+ "active": False,
+ "installable": True,
+}
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
diff --git a/addons/stock_planning/security/ir.model.access.csv b/addons/stock_planning/security/ir.model.access.csv
new file mode 100644
index 00000000000..b0ee2853b87
--- /dev/null
+++ b/addons/stock_planning/security/ir.model.access.csv
@@ -0,0 +1,7 @@
+"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
+"access_stock_planning","stock.planning","model_stock_planning","stock.group_stock_user",1,1,1,1
+"access_stock_period_create_lines","stock.period.createlines","model_stock_period_createlines","stock.group_stock_user",1,1,1,1
+"access_stock_period","stock.period","model_stock_period","stock.group_stock_user",1,1,1,1
+"access_stock_sale_forecast_createlines","stock.sale.forecast.createlines","model_stock_sale_forecast_createlines","stock.group_stock_user",1,1,1,1
+"access_stock_sale_forecast","stock.sale.forecast","model_stock_sale_forecast","stock.group_stock_user",1,1,1,1
+"access_stock_planning_createlines","stock.planning.createlines","model_stock_planning_createlines","stock.group_stock_user",1,1,1,1
diff --git a/addons/stock_planning/stock_planning.py b/addons/stock_planning/stock_planning.py
new file mode 100644
index 00000000000..fa0bec2f5b1
--- /dev/null
+++ b/addons/stock_planning/stock_planning.py
@@ -0,0 +1,1009 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2008 Tiny SPRL (). All Rights Reserved
+# $Id$
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from osv import fields,osv
+import pooler
+from tools import config
+import time
+import netsvc
+import math
+import mx.DateTime
+from mx.DateTime import RelativeDateTime, now, DateTime, localtime
+from tools.translate import _
+
+def rounding(fl, round_value):
+ if not round_value:
+ return fl
+ return round(fl / round_value) * round_value
+
+
+# Object creating periods quickly
+# changed that stop_date is created with hour 23:59:00 when it was 00:00:00 stop date was excluded from period
+class stock_period_createlines(osv.osv_memory):
+ _name = "stock.period.createlines"
+
+ def _get_new_period_start(self,cr,uid,context={}):
+ cr.execute("select max(date_stop) from stock_period")
+ result=cr.fetchone()
+ last_date = result and result[0] or False
+ if last_date:
+ period_start = mx.DateTime.strptime(last_date,"%Y-%m-%d %H:%M:%S")+ RelativeDateTime(days=1)
+ period_start = period_start - RelativeDateTime(hours=period_start.hour, minutes=period_start.minute, seconds=period_start.second)
+ else:
+ period_start = mx.DateTime.today()
+ return period_start.strftime('%Y-%m-%d')
+
+
+ _columns = {
+ 'name': fields.char('Period Name', size=64),
+ 'date_start': fields.date('Start Date', required=True),
+ 'date_stop': fields.date('End Date', required=True),
+ 'period_ids': fields.one2many('stock.period', 'planning_id', 'Periods'),
+ }
+ _defaults={
+ 'date_start':_get_new_period_start,
+ }
+
+ def create_period_weekly(self,cr, uid, ids, context={}):
+ res=self.create_period(cr, uid, ids, context, 6, 'Weekly')
+ return {
+ 'view_type': 'form',
+ "view_mode": 'tree',
+ 'res_model': 'stock.period',
+ 'type': 'ir.actions.act_window',
+ }
+
+ def create_period_monthly(self,cr, uid, ids, context={},interval=1):
+ for p in self.browse(cr, uid, ids, context):
+ dt = p.date_start
+ ds = mx.DateTime.strptime(p.date_start, '%Y-%m-%d')
+ while ds.strftime('%Y-%m-%d') val.product_id.uom_id.category_id.id:
+ coef = coef / val.product_id.uos_coeff
+ return val.product_id.uom_id.factor / coef
+
+ def _from_default_uom_factor(self, cr, uid, val, uom_id, context):
+ uom_obj = self.pool.get('product.uom')
+ uom = uom_obj.browse(cr, uid, uom_id, context=context)
+ res = uom.factor
+ if uom.category_id.id <> val.product_id.uom_id.category_id.id:
+ res = res / val.product_id.uos_coeff
+ return res / val.product_id.uom_id.factor, uom.rounding
+
+ def _sales_per_users(self, cr, uid, so, so_line, company, users):
+ cr.execute("SELECT sum(sol.product_uom_qty) FROM sale_order_line AS sol LEFT JOIN sale_order AS s ON (s.id = sol.order_id) " \
+ "WHERE (sol.id IN (%s)) AND (s.state NOT IN (\'draft\',\'cancel\')) AND (s.id IN (%s)) AND (s.company_id=%s) " \
+ "AND (s.user_id IN (%s)) " %(so_line, so, company, users))
+ ret = cr.fetchone()[0] or 0.0
+ return ret
+
+ def _sales_per_warehouse(self, cr, uid, so, so_line, company, shops):
+ cr.execute("SELECT sum(sol.product_uom_qty) FROM sale_order_line AS sol LEFT JOIN sale_order AS s ON (s.id = sol.order_id) " \
+ "WHERE (sol.id IN (%s)) AND (s.state NOT IN (\'draft\',\'cancel\')) AND (s.id IN (%s))AND (s.company_id=%s) " \
+ "AND (s.shop_id IN (%s))" %(so_line, so, company, shops))
+ ret = cr.fetchone()[0] or 0.0
+ return ret
+
+ def _sales_per_company(self, cr, uid, so, so_line, company, ):
+ cr.execute("SELECT sum(sol.product_uom_qty) FROM sale_order_line AS sol LEFT JOIN sale_order AS s ON (s.id = sol.order_id) " \
+ "WHERE (sol.id IN (%s)) AND (s.state NOT IN (\'draft\',\'cancel\')) AND (s.id IN (%s)) AND (s.company_id=%s)"%(so_line, so, company))
+ ret = cr.fetchone()[0] or 0.0
+ return ret
+
+ def calculate_sales_history(self, cr, uid, ids, context, *args):
+ sales=[[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],]
+ for obj in self.browse(cr, uid, ids):
+ periods =obj.analyzed_period1_id, obj.analyzed_period2_id, obj.analyzed_period3_id, obj.analyzed_period4_id, obj.analyzed_period5_id
+ so_obj = self.pool.get('sale.order')
+ so_line_obj= self.pool.get('sale.order.line')
+ so_line_product_ids = so_line_obj.search(cr, uid, [('product_id','=', obj.product_id.id)], context = context)
+ if so_line_product_ids:
+ so_line_product_set = ','.join(map(str,so_line_product_ids))
+ if obj.analyzed_warehouse_id:
+ shops = self.pool.get('sale.shop').search(cr, uid,[('warehouse_id','=', obj.analyzed_warehouse_id.id)], context = context)
+ shops_set = ','.join(map(str,shops))
+ else:
+ shops = False
+ if obj.analyzed_dept_id:
+ dept_obj = self.pool.get('hr.department')
+ dept_id = obj.analyzed_dept_id.id and [obj.analyzed_dept_id.id] or []
+ dept_ids = dept_obj.search(cr,uid,[('parent_id','child_of',dept_id)])
+ dept_ids_set = ','.join(map(str,dept_ids))
+ cr.execute("SELECT user_id FROM hr_department_user_rel WHERE (department_id IN (%s))" %(dept_ids_set))
+ dept_users = [x for x, in cr.fetchall()]
+ dept_users_set = ','.join(map(str,dept_users))
+ else:
+ dept_users = False
+ factor, round_value = self._from_default_uom_factor(cr, uid, obj, obj.product_uom.id, context=context)
+ for i, period in enumerate(periods):
+ if period:
+ so_period_ids = so_obj.search(cr, uid, [('date_order','>=',period.date_start), ('date_order','<=',period.date_stop)], context = context)
+ if so_period_ids:
+ so_period_set = ','.join(map(str,so_period_ids))
+ if obj.analyzed_user_id:
+ user_set = str(obj.analyzed_user_id.id)
+ sales[i][0] =self._sales_per_users(cr, uid, so_period_set, so_line_product_set, obj.company_id.id, user_set)
+ sales[i][0] *=factor
+ if dept_users:
+ sales[i][1]= self._sales_per_users(cr, uid, so_period_set, so_line_product_set, obj.company_id.id, dept_users_set)
+ sales[i][1]*=factor
+ if shops:
+ sales[i][2]= self._sales_per_warehouse(cr, uid, so_period_set, so_line_product_set, obj.company_id.id, shops_set)
+ sales[i][2]*=factor
+ if obj.analyze_company:
+ sales[i][3]= self._sales_per_company(cr, uid, so_period_set, so_line_product_set, obj.company_id.id, )
+ sales[i][3]*=factor
+ self.write(cr, uid, ids, {
+ 'analyzed_period1_per_user':sales[0][0],
+ 'analyzed_period2_per_user':sales[1][0],
+ 'analyzed_period3_per_user':sales[2][0],
+ 'analyzed_period4_per_user':sales[3][0],
+ 'analyzed_period5_per_user':sales[4][0],
+ 'analyzed_period1_per_dept':sales[0][1],
+ 'analyzed_period2_per_dept':sales[1][1],
+ 'analyzed_period3_per_dept':sales[2][1],
+ 'analyzed_period4_per_dept':sales[3][1],
+ 'analyzed_period5_per_dept':sales[4][1],
+ 'analyzed_period1_per_warehouse':sales[0][2],
+ 'analyzed_period2_per_warehouse':sales[1][2],
+ 'analyzed_period3_per_warehouse':sales[2][2],
+ 'analyzed_period4_per_warehouse':sales[3][2],
+ 'analyzed_period5_per_warehouse':sales[4][2],
+ 'analyzed_period1_per_company':sales[0][3],
+ 'analyzed_period2_per_company':sales[1][3],
+ 'analyzed_period3_per_company':sales[2][3],
+ 'analyzed_period4_per_company':sales[3][3],
+ 'analyzed_period5_per_company':sales[4][3],
+ })
+ return True
+
+
+stock_sale_forecast()
+
+# Creates stock planning records for products from selected Product Category for selected 'Warehouse - Period'
+# Object added by contributor in ver 1.1
+class stock_planning_createlines(osv.osv_memory):
+ _name = "stock.planning.createlines"
+
+ def onchange_company(self, cr, uid, ids, company_id):
+ result = {}
+ if company_id:
+ result['warehouse_id2'] = False
+ return {'value': result}
+
+ _columns = {
+ 'company_id': fields.many2one('res.company', 'Company', required = True),
+ 'period_id2': fields.many2one('stock.period' , 'Period', required=True, help = 'Period which planning will concern.'),
+ 'warehouse_id2': fields.many2one('stock.warehouse' , 'Warehouse', required=True, help = 'Warehouse which planning will concern.'),
+ 'product_categ_id2': fields.many2one('product.category' , 'Product Category', \
+ help = 'Planning will be created for products from Product Category selected by this field. '\
+ 'This field is ignored when you check \"All Forecasted Product\" box.' ),
+ 'forecasted_products': fields.boolean('All Products with Forecast', \
+ help = "Check this box to create planning for all products having any forecast for selected Warehouse and Period. "\
+ "Product Category field will be ignored."),
+ }
+
+ _defaults = {
+ 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.planning', context=c),
+ }
+
+ def create_planning(self,cr, uid, ids, context={}):
+ product_obj = self.pool.get('product.product')
+ planning_obj=self.pool.get('stock.planning')
+
+ for f in self.browse(cr, uid, ids, context=context):
+ if f.forecasted_products:
+ cr.execute("SELECT product_id \
+ FROM stock_sale_forecast \
+ WHERE (period_id = %s) AND (warehouse_id = %s)", (f.period_id2.id, f.warehouse_id2.id))
+ products_id1 = [x for x, in cr.fetchall()]
+ else:
+ prod_categ_obj=self.pool.get('product.category')
+ template_obj=self.pool.get('product.template')
+ categ_ids = f.product_categ_id2.id and [f.product_categ_id2.id] or []
+ prod_categ_ids=prod_categ_obj.search(cr,uid,[('parent_id','child_of',categ_ids)])
+ templates_ids = template_obj.search(cr,uid,[('categ_id','in',prod_categ_ids)])
+ products_id1 = product_obj.search(cr,uid,[('product_tmpl_id','in',templates_ids)])
+ if len(products_id1)==0:
+ raise osv.except_osv(_('Error !'), _('No forecasts for selected period or no products in selected category !'))
+
+ for p in product_obj.browse(cr, uid, products_id1,context=context):
+ if len(planning_obj.search(cr, uid, [('product_id','=',p.id),
+ ('period_id','=',f.period_id2.id),
+ ('warehouse_id','=',f.warehouse_id2.id)]))== 0:
+ cr.execute("SELECT period.date_stop, planning.product_uom, planning.planned_outgoing, planning.to_procure, \
+ planning.stock_only, planning.procure_to_stock, planning.confirmed_forecasts_only, \
+ planning.supply_warehouse_id, planning.stock_supply_location \
+ FROM stock_planning AS planning \
+ LEFT JOIN stock_period AS period \
+ ON planning.period_id = period.id \
+ WHERE (planning.create_uid = %s OR planning.write_uid = %s) \
+ AND planning.warehouse_id = %s AND planning.product_id = %s \
+ AND period.date_stop < %s \
+ ORDER BY period.date_stop DESC",
+ (uid, uid, f.warehouse_id2.id, p.id, f.period_id2.date_stop) )
+ ret=cr.fetchone()
+# forecast_qty = ret and ret[0] or 0.0
+ if ret:
+# raise osv.except_osv(_('Error !'), _('ret is %s %s %s %s %s %s')%(ret[0],ret[2],ret[3],ret[4],ret[5],ret[6],))
+ prod_uom = ret[1]
+ planned_out = ret[2]
+ to_procure = ret[3]
+ stock_only = ret[4]
+ procure_to_stock = ret[5]
+ confirmed_forecasts_only = ret[6]
+ supply_warehouse_id = ret[7]
+ stock_supply_location = ret[8]
+ else:
+ prod_uom = p.uom_id.id
+ planned_out = False
+ to_procure = False
+ stock_only = False
+ procure_to_stock = False
+ confirmed_forecasts_only = False
+ supply_warehouse_id = False
+ stock_supply_location = False
+ prod_uos_categ = False
+ if p.uos_id:
+ prod_uos_categ = p.uos_id.category_id.id
+ planning_obj.create(cr, uid, {
+ 'company_id' : f.warehouse_id2.company_id.id,
+ 'period_id': f.period_id2.id,
+ 'warehouse_id' : f.warehouse_id2.id,
+ 'product_id': p.id,
+ 'product_uom' : prod_uom,
+ 'product_uom_categ' : p.uom_id.category_id.id,
+ 'product_uos_categ' : prod_uos_categ,
+ 'active_uom' : prod_uom,
+ 'planned_outgoing': planned_out,
+ 'to_procure': to_procure,
+ 'stock_only': stock_only,
+ 'procure_to_stock': procure_to_stock,
+ 'confirmed_forecasts_only': confirmed_forecasts_only,
+ 'supply_warehouse_id': supply_warehouse_id,
+ 'stock_supply_location': stock_supply_location,
+
+ })
+ return {
+ 'view_type': 'form',
+ "view_mode": 'tree',
+ 'res_model': 'stock.planning',
+ 'type': 'ir.actions.act_window',
+ }
+stock_planning_createlines()
+
+
+# The main Stock Planning object
+# A lot of changes by contributor in ver 1.1
+class stock_planning(osv.osv):
+ _name = "stock.planning"
+
+ def _get_in_out(self, cr, uid, val, date_start, date_stop, direction, done, context):
+# res = {}
+# if not context:
+# context = {}
+ mapping = {'in': {
+ 'field': "incoming_qty",
+ 'adapter': lambda x: x,
+ },
+ 'out': {
+ 'field': "outgoing_qty",
+ 'adapter': lambda x: -x,
+ },
+ }
+ context['from_date'] = date_start
+ context['to_date'] = date_stop
+ locations = [val.warehouse_id.lot_stock_id.id,]
+ if not val.stock_only:
+ locations.extend([val.warehouse_id.lot_input_id.id, val.warehouse_id.lot_output_id.id])
+ context['location'] = locations
+ context['compute_child'] = True
+ product_obj = self.pool.get('product.product')
+ prod_id = val.product_id.id
+ if done:
+ c1=context.copy()
+ c1.update({ 'states':('done',), 'what':(direction,) })
+ prod_ids = [prod_id]
+ st = product_obj.get_product_available(cr,uid, prod_ids, context=c1)
+ res = mapping[direction]['adapter'](st.get(prod_id,0.0))
+ else:
+ product=product_obj.read(cr, uid, prod_id,[], context)
+ product_qty = product[mapping[direction]['field']]
+ res = mapping[direction]['adapter'](product_qty)
+# res[val.id] = product_obj['incoming_qty']
+ return res
+
+ def _get_outgoing_before(self, cr, uid, val, date_start, date_stop, context):
+ cr.execute("SELECT sum(planning.planned_outgoing), planning.product_uom \
+ FROM stock_planning AS planning \
+ LEFT JOIN stock_period AS period \
+ ON (planning.period_id = period.id) \
+ WHERE (period.date_stop >= %s) AND (period.date_stop <= %s) \
+ AND (planning.product_id = %s) AND (planning.company_id = %s) \
+ GROUP BY planning.product_uom", \
+ (date_start, date_stop, val.product_id.id, val.company_id.id,))
+ planning_qtys = cr.fetchall()
+ res = self._to_planning_uom(cr, uid, val, planning_qtys, context)
+ return res
+
+ def _to_planning_uom(self, cr, uid, val, qtys, context):
+ res_qty = 0
+ if qtys:
+ uom_obj = self.pool.get('product.uom')
+ for qty, prod_uom in qtys:
+ coef = self._to_default_uom_factor(cr, uid, val, prod_uom, context=context)
+ res_coef, round_value = self._from_default_uom_factor(cr, uid, val, val.product_uom.id, context=context)
+ coef = coef * res_coef
+ res_qty += rounding(qty * coef, round_value)
+ return res_qty
+
+
+ def _get_forecast(self, cr, uid, ids, field_names, arg, context):
+ res = {}
+ for val in self.browse(cr, uid, ids):
+ res[val.id]={}
+ valid_part = val.confirmed_forecasts_only and " AND state = 'validated'" or ""
+ cr.execute('SELECT sum(product_qty), product_uom \
+ FROM stock_sale_forecast \
+ WHERE product_id = %s AND period_id = %s AND company_id = %s '+valid_part+ \
+ 'GROUP BY product_uom', \
+ (val.product_id.id,val.period_id.id, val.company_id.id))
+ company_qtys = cr.fetchall()
+ res[val.id]['company_forecast'] = self._to_planning_uom(cr, uid, val, company_qtys, context)
+
+ cr.execute('SELECT sum(product_qty), product_uom \
+ FROM stock_sale_forecast \
+ WHERE product_id = %s and period_id = %s AND warehouse_id = %s ' + valid_part + \
+ 'GROUP BY product_uom', \
+ (val.product_id.id,val.period_id.id, val.warehouse_id.id))
+ warehouse_qtys = cr.fetchall()
+ res[val.id]['warehouse_forecast'] = self._to_planning_uom(cr, uid, val, warehouse_qtys, context)
+ res[val.id]['warehouse_forecast'] = rounding(res[val.id]['warehouse_forecast'], val.product_id.uom_id.rounding)
+ return res
+
+ def _get_stock_start(self, cr, uid, val, date, context):
+ context['from_date'] = None
+ context['to_date'] = date
+ locations = [val.warehouse_id.lot_stock_id.id,]
+ if not val.stock_only:
+ locations.extend([val.warehouse_id.lot_input_id.id, val.warehouse_id.lot_output_id.id])
+ context['location'] = locations
+ context['compute_child'] = True
+ product_obj = self.pool.get('product.product').read(cr, uid,val.product_id.id,[], context)
+ res = product_obj['qty_available'] # value for stock_start
+ return res
+
+ def _get_past_future(self, cr, uid, ids, field_names, arg, context):
+ res = {}
+ for val in self.browse(cr, uid, ids, context=context):
+ if val.period_id.date_stop < time.strftime('%Y-%m-%d'):
+ res[val.id] = 'Past'
+ else:
+ res[val.id] = 'Future'
+ return res
+
+ def _get_op(self, cr, uid, ids, field_names, arg, context): # op = OrderPoint
+ res = {}
+ for val in self.browse(cr, uid, ids, context=context):
+ res[val.id]={}
+ cr.execute("SELECT product_min_qty, product_max_qty, product_uom \
+ FROM stock_warehouse_orderpoint \
+ WHERE warehouse_id = %s AND product_id = %s AND active = 'TRUE'", (val.warehouse_id.id, val.product_id.id))
+ ret = cr.fetchone() or [0.0,0.0,False]
+ coef = 1
+ round_value = 1
+ if ret[2]:
+ coef = self._to_default_uom_factor(cr, uid, val, ret[2], context)
+ res_coef, round_value = self._from_default_uom_factor(cr, uid, val, val.product_uom.id, context=context)
+ coef = coef * res_coef
+ res[val.id]['minimum_op'] = rounding(ret[0]*coef, round_value)
+ res[val.id]['maximum_op'] = ret[1]*coef
+ return res
+
+ def onchange_company(self, cr, uid, ids, company_id):
+ result = {}
+ if company_id:
+ result['warehouse_id'] = False
+ return {'value': result}
+
+ def onchange_uom(self, cr, uid, ids, product_uom, ):
+ if not product_uom:
+ return {}
+ ret={}
+ val1 = self.browse(cr, uid, ids)
+ val = val1[0]
+ coeff_uom2def = self._to_default_uom_factor(cr, uid, val, val.active_uom.id, {})
+ coeff_def2uom, round_value = self._from_default_uom_factor( cr, uid, val, product_uom, {})
+ coeff = coeff_uom2def * coeff_def2uom
+ ret['planned_outgoing'] = rounding(coeff * val.planned_outgoing, round_value)
+ ret['to_procure'] = rounding(coeff * val.to_procure, round_value)
+ ret['active_uom'] = product_uom
+ return {'value': ret}
+
+ _columns = {
+ 'company_id': fields.many2one('res.company', 'Company', required = True),
+ 'history' : fields.text('Procurement History', readonly=True, help = "History of procurement or internal supply of this planning line."),
+ 'state' : fields.selection([('draft','Draft'),('done','Done')],'State',readonly=True),
+ 'period_id': fields.many2one('stock.period' , 'Period', required=True, \
+ help = 'Period for this planning. Requisition will be created for beginning of the period.'),
+ 'warehouse_id' : fields.many2one('stock.warehouse','Warehouse', required=True),
+ 'product_id': fields.many2one('product.product' , 'Product', required=True, help = 'Product which this planning is created for.'),
+ 'product_uom_categ' : fields.many2one('product.uom.categ', 'Product UoM Category'), # Invisible field for product_uom domain
+ 'product_uom' : fields.many2one('product.uom', 'UoM', required=True, help = "Unit of Measure used to show the quanities of stock calculation." \
+ "You can use units form default category or from second category (UoS category)."),
+ 'product_uos_categ' : fields.many2one('product.uom.categ', 'Product UoM Category'), # Invisible field for product_uos domain
+# Field used in onchange_uom to check what uom was before change to recalculate quantities acording to old uom (active_uom) and new uom.
+ 'active_uom' :fields.many2one('product.uom', string = "Active UoM"),
+ 'planned_outgoing' : fields.float('Planned Out', required=True, \
+ help = 'Enter planned outgoing quantity from selected Warehouse during the selected Period of selected Product. '\
+ 'To plan this value look at Confirmed Out or Sales Forecasts. This value should be equal or greater than Confirmed Out.'),
+ 'company_forecast': fields.function(_get_forecast, method=True, string ='Company Forecast', multi = 'company', \
+ help = 'All sales forecasts for whole company (for all Warehouses) of selected Product during selected Period.'),
+ 'warehouse_forecast': fields.function(_get_forecast, method=True, string ='Warehouse Forecast', multi = 'warehouse',\
+ help = 'All sales forecasts for selected Warehouse of selected Product during selected Period.'),
+ 'stock_simulation': fields.float('Stock Simulation', readonly =True, \
+ help = 'Stock simulation at the end of selected Period.\n For current period it is: \n' \
+ 'Initial Stock - Already Out + Already In - Expected Out + Incoming Left.\n' \
+ 'For periods ahead it is: \nInitial Stock - Planned Out Before + Incoming Before - Planned Out + Planned In.'),
+ 'incoming': fields.float('Confirmed In', readonly=True, \
+ help = 'Quantity of all confirmed incoming moves in calculated Period.'),
+ 'outgoing': fields.float('Confirmed Out', readonly=True, \
+ help = 'Quantity of all confirmed outgoing moves in calculated Period.'),
+ 'incoming_left': fields.float('Incoming Left', readonly=True, \
+ help = 'Quantity left to Planned incoming quantity. This is calculated difference between Planned In and Confirmed In. ' \
+ 'For current period Already In is also calculated. This value is used to create procurement for lacking quantity.'),
+ 'outgoing_left': fields.float('Expected Out', readonly=True, \
+ help = 'Quantity expected to go out in selected period. As a difference between Planned Out and Confirmed Out. ' \
+ 'For current period Already Out is also calculated'),
+ 'to_procure': fields.float(string='Planned In', required=True, \
+ help = 'Enter quantity which (by your plan) should come in. Change this value and observe Stock simulation. ' \
+ 'This value should be equal or greater than Confirmed In.'),
+ 'line_time' : fields.function(_get_past_future, method=True,type='char', string='Past/Future'),
+ 'minimum_op' : fields.function(_get_op, method=True, type='float', string = 'Minimum Rule', multi= 'minimum', \
+ help = 'Minimum quantity set in Minimum Stock Rules for this Warhouse'),
+ 'maximum_op' : fields.function(_get_op, method=True, type='float', string = 'Maximum Rule', multi= 'maximum', \
+ help = 'Maximum quantity set in Minimum Stock Rules for this Warhouse'),
+ 'outgoing_before' : fields.float('Planned Out Before', readonly=True, \
+ help= 'Planned Out in periods before calculated. '\
+ 'Between start date of current period and one day before start of calculated period.'),
+ 'incoming_before': fields.float('Incoming Before', readonly = True, \
+ help= 'Confirmed incoming in periods before calculated (Including Already In). '\
+ 'Between start date of current period and one day before start of calculated period.'),
+ 'stock_start' : fields.float('Initial Stock', readonly=True, \
+ help= 'Stock quantity one day before current period.'),
+ 'already_out' : fields.float('Already Out', readonly=True, \
+ help= 'Quantity which is already dispatched out of this warehouse in current period.'),
+ 'already_in' : fields.float('Already In', readonly=True, \
+ help= 'Quantity which is already picked up to this warehouse in current period.'),
+ 'stock_only' : fields.boolean("Stock Location Only", help = "Check to calculate stock location of selected warehouse only. " \
+ "If not selected calculation is made for input, stock and output location of warehouse."),
+ "procure_to_stock" : fields.boolean("Procure To Stock Location", help = "Chect to make procurement to stock location of selected warehouse. " \
+ "If not selected procurement will be made into input location of warehouse."),
+ "confirmed_forecasts_only" : fields.boolean("Validated Forecasts", help = "Check to take validated forecasts only. " \
+ "If not checked system takes validated and draft forecasts."),
+ 'supply_warehouse_id' : fields.many2one('stock.warehouse','Source Warehouse', help = "Warehouse used as source in supply pick move created by 'Supply from Another Warhouse'."),
+ "stock_supply_location" : fields.boolean("Stock Supply Location", help = "Check to supply from Stock location of Supply Warehouse. " \
+ "If not checked supply will be made from Output location of Supply Warehouse. Used in 'Supply from Another Warhouse' with Supply Warehouse."),
+
+ }
+
+ _defaults = {
+ 'state': lambda *args: 'draft' ,
+ 'to_procure' : 0.0,
+ 'planned_outgoing' : 0.0,
+ 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.planning', context=c),
+ }
+
+ _order = 'period_id'
+
+ def _to_default_uom_factor(self, cr, uid, val, uom_id, context):
+ uom_obj = self.pool.get('product.uom')
+ uom = uom_obj.browse(cr, uid, uom_id, context=context)
+ coef = uom.factor
+ if uom.category_id.id != val.product_id.uom_id.category_id.id:
+ coef = coef / val.product_id.uos_coeff
+ return val.product_id.uom_id.factor / coef
+
+
+ def _from_default_uom_factor(self, cr, uid, val, uom_id, context):
+ uom_obj = self.pool.get('product.uom')
+ uom = uom_obj.browse(cr, uid, uom_id, context=context)
+ res = uom.factor
+ if uom.category_id.id != val.product_id.uom_id.category_id.id:
+ res = res / val.product_id.uos_coeff
+ return res / val.product_id.uom_id.factor, uom.rounding
+
+ def calculate_planning(self, cr, uid, ids, context, *args):
+ one_minute = RelativeDateTime(minutes=1)
+ current_date_beginning_c = mx.DateTime.today()
+ current_date_end_c = current_date_beginning_c + RelativeDateTime(days=1, minutes=-1) # to get hour 23:59:00
+ current_date_beginning = current_date_beginning_c.strftime('%Y-%m-%d %H:%M:%S')
+ current_date_end = current_date_end_c.strftime('%Y-%m-%d %H:%M:%S')
+ for val in self.browse(cr, uid, ids, context=context):
+ day = mx.DateTime.strptime(val.period_id.date_start, '%Y-%m-%d %H:%M:%S')
+ dbefore = mx.DateTime.DateTime(day.year, day.month, day.day) - one_minute
+ day_before_calculated_period = dbefore.strftime('%Y-%m-%d %H:%M:%S') # one day before start of calculated period
+ cr.execute("SELECT date_start \
+ FROM stock_period AS period \
+ LEFT JOIN stock_planning AS planning \
+ ON (planning.period_id = period.id) \
+ WHERE (period.date_stop >= %s) AND (period.date_start <= %s) AND \
+ planning.product_id = %s", (current_date_end, current_date_end, val.product_id.id,)) #
+ date = cr.fetchone()
+ start_date_current_period = date and date[0] or False
+ start_date_current_period = start_date_current_period or current_date_beginning
+ day = mx.DateTime.strptime(start_date_current_period, '%Y-%m-%d %H:%M:%S')
+ dbefore = mx.DateTime.DateTime(day.year, day.month, day.day) - one_minute
+ date_for_start = dbefore.strftime('%Y-%m-%d %H:%M:%S') # one day before current period
+ already_out = self._get_in_out(cr, uid, val, start_date_current_period, current_date_end, direction='out', done = True, context=context),
+ already_in = self._get_in_out(cr, uid, val, start_date_current_period, current_date_end, direction='in', done = True, context=context),
+ outgoing = self._get_in_out(cr, uid, val, val.period_id.date_start, val.period_id.date_stop, direction='out', done = False, context=context),
+ incoming = self._get_in_out(cr, uid, val, val.period_id.date_start, val.period_id.date_stop, direction='in', done = False, context=context),
+ outgoing_before = self._get_outgoing_before(cr, uid, val, start_date_current_period, day_before_calculated_period, context=context),
+ incoming_before = self._get_in_out(cr, uid, val, start_date_current_period, day_before_calculated_period, direction='in', done = False, context=context),
+ stock_start = self._get_stock_start(cr, uid, val, date_for_start, context=context),
+ if start_date_current_period == val.period_id.date_start: # current period is calculated
+ current=True
+ else:
+ current=False
+ factor, round_value = self._from_default_uom_factor(cr, uid, val, val.product_uom.id, context=context)
+ self.write(cr, uid, ids, {
+ 'already_out': rounding(already_out[0]*factor,round_value),
+ 'already_in': rounding(already_in[0]*factor,round_value),
+ 'outgoing': rounding(outgoing[0]*factor,round_value),
+ 'incoming': rounding(incoming[0]*factor,round_value),
+ 'outgoing_before' : rounding(outgoing_before[0]*factor,round_value),
+ 'incoming_before': rounding((incoming_before[0]+ (not current and already_in[0]))*factor,round_value),
+ 'outgoing_left': rounding(val.planned_outgoing - (outgoing[0] + (current and already_out[0]))*factor,round_value),
+ 'incoming_left': rounding(val.to_procure - (incoming[0] + (current and already_in[0]))*factor,round_value),
+ 'stock_start' : rounding(stock_start[0]*factor,round_value),
+ 'stock_simulation': rounding(val.to_procure - val.planned_outgoing + (stock_start[0]+ incoming_before[0] - outgoing_before[0] \
+ + (not current and already_in[0]))*factor,round_value),
+ })
+ return True
+
+# method below converts quantities and uoms to general OpenERP standard with UoM Qty, UoM, UoS Qty, UoS.
+# from stock_planning standard where you have one Qty and one UoM (any from UoM or UoS category)
+# so if UoM is from UoM category it is used as UoM in standard and if product has UoS the UoS will be calcualated.
+# If UoM is from UoS category it is recalculated to basic UoS from product (in planning you can use any UoS from UoS category)
+# and basic UoM is calculated.
+ def _qty_to_standard(self, cr, uid, val, context):
+ uos = False
+ uos_qty = 0.0
+ if val.product_uom.category_id.id == val.product_id.uom_id.category_id.id:
+ uom_qty = val.incoming_left
+ uom = val.product_uom.id
+ if val.product_id.uos_id:
+ uos = val.product_id.uos_id.id
+ coeff_uom2def = self._to_default_uom_factor(cr, uid, val, val.product_uom.id, {})
+ coeff_def2uom, round_value = self._from_default_uom_factor(cr, uid, val, uos, {})
+ uos_qty = rounding(val.incoming_left * coeff_uom2def * coeff_def2uom, round_value)
+ elif val.product_uom.category_id.id == val.product_id.uos_id.category_id.id:
+ coeff_uom2def = self._to_default_uom_factor(cr, uid, val, val.product_uom.id, {})
+ uos = val.product_id.uos_id.id
+ coeff_def2uom, round_value = self._from_default_uom_factor(cr, uid, val, uos, {})
+ uos_qty = rounding(val.incoming_left * coeff_uom2def * coeff_def2uom, round_value)
+ uom = val.product_id.uom_id.id
+ coeff_def2uom, round_value = self._from_default_uom_factor(cr, uid, val, uom, {})
+ uom_qty = rounding(val.incoming_left * coeff_uom2def * coeff_def2uom, round_value)
+ return uom_qty, uom, uos_qty, uos
+
+ def procure_incomming_left(self, cr, uid, ids, context, *args):
+ for obj in self.browse(cr, uid, ids):
+ if obj.incoming_left <= 0:
+ raise osv.except_osv(_('Error !'), _('Incoming Left must be greater than 0 !'))
+ uom_qty, uom, uos_qty, uos = self._qty_to_standard(cr, uid, obj, context)
+ user = self.pool.get('res.users').browse(cr, uid, uid, context)
+ proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
+ 'company_id' : obj.company_id.id,
+ 'name': _('Manual planning for ') + obj.period_id.name,
+ 'origin': _('MPS(') + str(user.login) +') '+ obj.period_id.name,
+ 'date_planned': obj.period_id.date_start,
+ 'product_id': obj.product_id.id,
+ 'product_qty': uom_qty,
+ 'product_uom': uom,
+ 'product_uos_qty': uos_qty,
+ 'product_uos': uos,
+ 'location_id': obj.procure_to_stock and obj.warehouse_id.lot_stock_id.id or obj.warehouse_id.lot_input_id.id,
+ 'procure_method': 'make_to_order',
+ 'note' : _("Procurement created in MPS by user: ") + str(user.login) + _(" Creation Date: ") + \
+ time.strftime('%Y-%m-%d %H:%M:%S') + \
+ _("\nFor period: ") + obj.period_id.name + _(" according to state:") + \
+ _("\n Warehouse Forecast: ") + str(obj.warehouse_forecast) + \
+ _("\n Initial Stock: ") + str(obj.stock_start) + \
+ _("\n Planned Out: ") + str(obj.planned_outgoing) + _(" Planned In: ") + str(obj.to_procure) + \
+ _("\n Already Out: ") + str(obj.already_out) + _(" Already In: ") + str(obj.already_in) + \
+ _("\n Confirmed Out: ") + str(obj.outgoing) + _(" Confirmed In: ") + str(obj.incoming) + \
+ _("\n Planned Out Before: ") + str(obj.outgoing_before) + _(" Confirmed In Before: ") + \
+ str(obj.incoming_before) + \
+ _("\n Expected Out: ") + str(obj.outgoing_left) + _(" Incoming Left: ") + str(obj.incoming_left) + \
+ _("\n Stock Simulation: ") + str(obj.stock_simulation) + _(" Minimum stock: ") + str(obj.minimum_op),
+
+ }, context=context)
+ wf_service = netsvc.LocalService("workflow")
+ wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
+ self.calculate_planning(cr, uid, ids, context)
+ prev_text = obj.history or ""
+ self.write(cr, uid, ids, {
+ 'history' : prev_text + _('Requisition (') + str(user.login) + ", " + time.strftime('%Y.%m.%d %H:%M) ') + str(obj.incoming_left) + \
+ " " + obj.product_uom.name + "\n",
+ })
+ return True
+
+ def internal_supply(self, cr, uid, ids, context, *args):
+ for obj in self.browse(cr, uid, ids):
+ if obj.incoming_left <= 0:
+ raise osv.except_osv(_('Error !'), _('Incoming Left must be greater than 0 !'))
+ if not obj.supply_warehouse_id:
+ raise osv.except_osv(_('Error !'), _('You must specify a Source Warehouse !'))
+ if obj.supply_warehouse_id.id == obj.warehouse_id.id:
+ raise osv.except_osv(_('Error !'), _('You must specify a Source Warehouse different than calculated (destination) Warehouse !'))
+ uom_qty, uom, uos_qty, uos = self._qty_to_standard(cr, uid, obj, context)
+ user = self.pool.get('res.users').browse(cr, uid, uid, context)
+ picking_id = self.pool.get('stock.picking').create(cr, uid, {
+ 'origin': _('MPS(') + str(user.login) +') '+ obj.period_id.name,
+ 'type': 'internal',
+ 'state': 'auto',
+ 'date' : obj.period_id.date_start,
+ 'move_type': 'direct',
+ 'invoice_state': 'none',
+ 'company_id': obj.company_id.id,
+ 'note': _("Pick created from MPS by user: ") + str(user.login) + _(" Creation Date: ") + \
+ time.strftime('%Y-%m-%d %H:%M:%S') + \
+ _("\nFor period: ") + obj.period_id.name + _(" according to state:") + \
+ _("\n Warehouse Forecast: ") + str(obj.warehouse_forecast) + \
+ _("\n Initial Stock: ") + str(obj.stock_start) + \
+ _("\n Planned Out: ") + str(obj.planned_outgoing) + _(" Planned In: ") + str(obj.to_procure) + \
+ _("\n Already Out: ") + str(obj.already_out) + _(" Already In: ") + str(obj.already_in) + \
+ _("\n Confirmed Out: ") + str(obj.outgoing) + _(" Confirmed In: ") + str(obj.incoming) + \
+ _("\n Planned Out Before: ") + str(obj.outgoing_before) + _(" Confirmed In Before: ") + \
+ str(obj.incoming_before) + \
+ _("\n Expected Out: ") + str(obj.outgoing_left) + _(" Incoming Left: ") + str(obj.incoming_left) + \
+ _("\n Stock Simulation: ") + str(obj.stock_simulation) + _(" Minimum stock: ") + str(obj.minimum_op),
+ })
+
+ move_id = self.pool.get('stock.move').create(cr, uid, {
+ 'name': _('MPS(') + str(user.login) +') '+ obj.period_id.name,
+ 'picking_id': picking_id,
+ 'product_id': obj.product_id.id,
+ 'date_planned': obj.period_id.date_start,
+ 'product_qty': uom_qty,
+ 'product_uom': uom,
+ 'product_uos_qty': uos_qty,
+ 'product_uos': uos,
+ 'location_id': obj.stock_supply_location and obj.supply_warehouse_id.lot_stock_id.id or \
+ obj.supply_warehouse_id.lot_output_id.id,
+ 'location_dest_id': obj.procure_to_stock and obj.warehouse_id.lot_stock_id.id or \
+ obj.warehouse_id.lot_input_id.id,
+ 'tracking_id': False,
+ 'company_id': obj.company_id.id,
+ })
+ wf_service = netsvc.LocalService("workflow")
+ wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
+
+ self.calculate_planning(cr, uid, ids, context)
+ prev_text = obj.history or ""
+ pick_name = self.pool.get('stock.picking').browse(cr, uid, picking_id).name
+ self.write(cr, uid, ids, {
+ 'history' : prev_text + _('Pick List ')+ pick_name + " (" + str(user.login) + ", " + time.strftime('%Y.%m.%d %H:%M) ') \
+ + str(obj.incoming_left) +" " + obj.product_uom.name + "\n",
+ })
+
+ return True
+
+ def product_id_change(self, cr, uid, ids, product_id):
+ ret={}
+ if product_id:
+ product_rec = self.pool.get('product.product').browse(cr, uid, product_id)
+ ret['product_uom'] = product_rec.uom_id.id
+ ret['active_uom'] = product_rec.uom_id.id
+ ret['product_uom_categ'] = product_rec.uom_id.category_id.id
+ ret['product_uos_categ'] = product_rec.uos_id and product_rec.uos_id.category_id.id or False
+ else:
+ ret['product_uom'] = False
+ ret['product_uom_categ'] = False
+ ret['product_uos_categ'] = False
+ res = {'value': ret}
+ return res
+
+stock_planning()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/stock_planning/stock_planning_view.xml b/addons/stock_planning/stock_planning_view.xml
new file mode 100644
index 00000000000..e90d68cf855
--- /dev/null
+++ b/addons/stock_planning/stock_planning_view.xml
@@ -0,0 +1,432 @@
+
+
+
+
+
+
+
+
+ stock.period.createlines.form
+ stock.period.createlines
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Stock and Sales Planning Periods
+ stock.period.createlines
+ form
+ form
+
+ new
+
+
+
+
+
+
+
+ stock.period.form
+ stock.period
+ form
+
+
+
+
+
+
+
+
+
+
+
+ stock.period.tree
+ stock.period
+ tree
+
+
+
+
+
+
+
+
+
+
+
+ Stock and Sales Periods
+ stock.period
+ form
+ tree,form
+
+
+
+
+
+
+
+
+ stock.sale.forecast.createlines
+ stock.sale.forecast.createlines
+ form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create Stock and Sales Forecasts
+ stock.sale.forecast.createlines
+ form
+ form
+
+ new
+
+
+
+
+
+
+
+ stock.sale.forecast.form
+ stock.sale.forecast
+ form
+
+