[MERGE] useability improvement

bzr revid: fp@tinyerp.com-20121102083612-xb2qvaqhgxunfipg
This commit is contained in:
Fabien Pinckaers 2012-11-02 09:36:12 +01:00
commit 726a8e6c10
53 changed files with 27 additions and 50985 deletions

View File

@ -144,8 +144,8 @@
<field name="product_id" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id, journal_id)"/>
<label for="unit_amount"/>
<div>
<field name="unit_amount" class="oe_inline"/>
<field name="product_uom_id" class="oe_inline"/>
<field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id)" class="oe_inline"/>
<field name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id)" class="oe_inline"/>
</div>
</group>
<group string="General Accounting">

View File

@ -10,6 +10,12 @@
<field name="domain">[('invoice_id','=',False)]</field>
<field name="context">{'search_default_to_invoice': 1}</field>
<field name="search_view_id" ref="account.view_account_analytic_line_filter"/>
<field name="help" type="html">
<p>
You will find here timesheets and purchases you did for contracts that can be reinvoiced to the customer.
If you want to record new jobs to invoice, you should use the timesheet menu instead.
</p>
</field>
</record>
<menuitem action="action_hr_tree_invoiced_all" id="menu_action_hr_tree_invoiced_all" parent="base.menu_invoiced" sequence="5"/>

View File

@ -106,12 +106,6 @@
states="draft,pending" groups="base.group_user"/>
<button name="case_close" string="Done" type="object" class="oe_highlight"
states="open,pending" groups="base.group_user"/>
<button name="case_refuse" string="Refuse" type="object" class="oe_highlight"
states="draft,open,pending" groups="base.group_user"/>
<button name="stage_previous" string="Previous Stage" type="object" groups="base.group_user"
states="open,pending" icon="gtk-go-back" attrs="{'invisible': [('stage_id','=', False)]}"/>
<button name="stage_next" string="Next Stage" type="object" groups="base.group_user"
states="open,pending" icon="gtk-go-forward" attrs="{'invisible': [('stage_id','=', False)]}"/>
<button name="case_reset" string="Reset to Draft" type="object" groups="base.group_user"
states="cancel,done"/>
<button name="case_cancel" string="Cancel" type="object" groups="base.group_user"

View File

@ -94,13 +94,13 @@ class account_analytic_account(osv.osv):
def set_close(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'close'}, context=context)
message = _("Contract has been <b>closed</b>.")
self.message_post(cr, uid, ids, body=message, subtype="mt_account_closed", context=context)
self.message_post(cr, uid, ids, body=message, subtype="hr_timesheet_invoice.mt_account_closed", context=context)
return True
def set_cancel(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
message = _("Contract has been <b>canceled</b>.")
self.message_post(cr, uid, ids, body=message, subtype="mt_account_canceled", context=context)
self.message_post(cr, uid, ids, body=message, subtype="hr_timesheet_invoice.mt_account_canceled", context=context)
return True
def set_open(self, cr, uid, ids, context=None):

View File

@ -21,5 +21,16 @@
<field name="customer_name">80%</field>
<field name="factor">20.0</field>
</record>
<!-- mail: subtypes -->
<record id="mt_account_closed" model="mail.message.subtype">
<field name="name">finished</field>
<field name="res_model">account.analytic.account</field>
</record>
<record id="mt_account_canceled" model="mail.message.subtype">
<field name="name">canceled</field>
<field name="res_model">account.analytic.account</field>
</record>
</data>
</openerp>

View File

@ -19,10 +19,10 @@
<xpath expr="/form/sheet" position='before'>
<header>
<button name="set_pending" string="Pending" type="object" states="open" />
<button name="set_close" string="Close" type="object" states="open,pending" />
<button name="set_close" string="Contract Finished" type="object" states="open,pending" />
<button name="set_open" string="Re-open project" type="object" states="pending,close" />
<button name="set_open" string="Re-open project" type="object" states="cancelled,draft"/>
<button name="set_cancel" string="Cancel" type="object" states="open,pending"/>
<button name="set_cancel" string="Cancel Contract" type="object" states="open,pending"/>
<field name="state" readonly="1" widget="statusbar"
statusbar_visible="open,pending,close" statusbar_colors='{"pending":"red", "template":"blue"}'/>
</header>

View File

@ -86,11 +86,9 @@
<field name="context">{'search_default_to_invoice': 1}</field>
<field name="view_id" ref="view_account_analytic_line_tree_inherit_account_id"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a work to invoice.
</p><p>
You will find here all works made on tasks that you can
invoice.
<p>
You will find here timesheets and purchases you did for contracts that can be reinvoiced to the customer.
If you want to record new jobs to invoice, you should use the timesheet menu instead.
</p>
</field>
</record>

View File

@ -948,6 +948,7 @@ class sale_order_line(osv.osv):
elif uom: # whether uos is set or not
default_uom = product_obj.uom_id and product_obj.uom_id.id
q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
result['product_uom'] = default_uom
if product_obj.uos_id:
result['product_uos'] = product_obj.uos_id.id
result['product_uos_qty'] = qty * product_obj.uos_coeff

View File

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import stock_planning
import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,248 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Master Procurement Schedule',
'version': '1.2',
'author': 'OpenERP SA and Grzegorz Grzelak (OpenGLOBE)',
'category' : 'Manufacturing',
'images': ['images/master_procurement_schedule.jpeg','images/sales_forecast.jpeg','images/stock_planning_line.jpeg','images/stock_sales_period.jpeg'],
'depends': ['crm', 'stock','sale'],
'description': """
MPS allows to create a manual procurement plan apart of the normal MRP scheduling, which works automatically based on minimum stock rules.
==========================================================================================================================================
Quick Glossary:
---------------
- Stock Period - the time boundaries (between Start Date and End Date) for
your Sales and Stock forecasts and planning
- Sales Forecast - the quantity of products you plan to sell during the
related Stock Period.
- Stock Planning - the quantity of products you plan to purchase or produce
for the related Stock Period.
To avoid confusion with the terms used by the ``sale_forecast`` module,
('Sales Forecast' and 'Planning' are amounts) we use terms 'Stock and Sales
Forecast' and 'Stock Planning' to emphasize that we use quantity values.
Where to begin:
---------------
Using this module is done in three steps:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Create Stock Periods via the **Warehouse** > **Configuration** > **Stock Periods** menu
(Mandatory step)
* Create Sale Forecasts fill them with forecast quantities, via the
**Sales** > **Sales Forecast** menu. (Optional step but useful for further planning)
* Create the actual MPS plan, check the balance and trigger the procurements
as required. The actual procurement is the final step for the Stock Period.
Stock Period configuration:
---------------------------
You have two menu items for Periods in "**Warehouse** > **Configuration** > **Stock Periods**".
There are:
~~~~~~~~~~
* "Create Stock Periods" - can automatically creating daily, weekly or
monthly periods.
* 'Stock Periods' - allows to create any type of periods, change the dates
and change the state of period.
Creating periods is the first step. You can create custom periods using the 'New'
button in 'Stock Periods', but it is recommended to use the automatic assistant
'Create Stock Periods'.
Remarks:
++++++++
- These periods (Stock Periods) are completely distinct from Financial or
other periods in the system.
- Periods are not assigned to companies (when you use multicompany). 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
start hour 00:00:00 and end hour 23:59:00. 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 configuration:
------------------------------
You have few menus for Sales forecast in "**Sales** > **Sales Forecasts**":
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- "Create Sales Forecasts" - can automatically create forecast lines
according to your needs
- 'Sales Forecasts' - for managing the Sales forecasts
Menu 'Create Sales Forecasts' creates Forecasts for products from selected
Category, for selected Period and for selected Warehouse.
It is also possible to copy the previous forecast.
Remarks:
++++++++
- This tool doesn't duplicate lines if you already have an entry for the same
Product, Period, Warehouse, created or validated by the same user. If you
wish to create another forecast, if relevant lines exists you have to do it
manually as described below.
- When created lines are validated by someone else you can use this tool to
create another line for the same Period, Product and Warehouse.
- When you choose 'Copy Last Forecast', created line take quantity and other
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.
On 'Sales Forecast' form mainly you have to enter a forecast quantity in
'Product Quantity'. Further 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 may enter the amount of forecast sales via the
'Product Amount' field. The 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 a unit of measure from the default category or from secondary category.
When you change unit of measure the forecast product quantity will be re-computed
according to new UoM.
To work out your Sale Forecast you can use the 'Sales History' of the 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 get results for
a given sales team or period.
MPS or Procurement Planning:
----------------------------
An MPS planning consists in Stock Planning lines, used to analyze and possibly
drive the procurement of products for each relevant Stock Period and Warehouse.
The menu is located in "**Warehouse** > **Schedulers** > **Master Procurement Schedule**":
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- "Create Stock Planning Lines" - a wizard to help automatically create many
planning lines
- 'Master Procurement Schedule' - management of your planning lines
Similarly to the way Sales forecast serves to define your sales planning, the MPS
lets you plan your procurements (Purchase/Manufacturing).You can quickly populate
the MPS with the 'Create Stock Planning Lines' wizard, and then proceed to review
them via the 'Master Procurement Schedule' menu.
The 'Create Stock Planning Lines' wizard lets you to quickly create all MPS lines
for a given Product Category, and a given Period and Warehouse.When you enable
the 'All Products with Forecast' option of the wizard, the system creates lines
for all products having sales forecast for selected Period and Warehouse (the
selected Category will be ignored in this case).
Under menu 'Master Procurement Schedule' you will usually change the 'Planned Out'
and 'Planned In' quantities and observe the resulting 'Stock Simulation' value
to decide if you need to procure more products for the given Period. 'Planned Out'
will be initially based on 'Warehouse Forecast' which is the sum of all outgoing
stock moves already planned for the Period and Warehouse. Of course you can alter
this value to provide your own quantities. 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 reach the 'Stock Simulation' at the end of Period. You
can compare 'Stock Simulation' quantity to minimum stock rules visible on the form.
And you can plan different quantity than in Minimum Stock Rules. Calculations are
done for whole Warehouse by default, if you want to see values for Stock location
of calculated warehouse you can check 'Stock Location Only'.
When you are satisfied with the 'Planned Out', 'Planned In' and end of period
'Stock Simulation', you can click on 'Procure Incoming Left' to create a
procurement for the 'Incoming Left' quantity. You can decide if procurement will
go to the to Stock or Input location of the Warehouse.
If you don't want to Produce or Buy the product but just transfer the calculated
quantity from another warehouse you can click 'Supply from Another Warehouse'
(instead of 'Procure Incoming Left') and the system will create the appropriate
picking list (stock moves). You can choose to take the goods from the Stock or
the Output location of the source warehouse. Destination location (Stock or Input)
in the destination warehouse will be taken as for the procurement case.
To see update the quantities of 'Confirmed In', 'Confirmed Out', 'Confirmed In
Before', 'Planned Out Before' and 'Stock Simulation' you can press '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 secondary category.
When you change unit of measure the editable quantities will be re-computed
according to new UoM. The others will be updated after pressing 'Calculate Planning'.
Computation of Stock Simulation quantities:
-------------------------------------------
The Stock Simulation value is the estimated stock quantity at the end of the
period. The calculation always starts with the real stock on hand at the beginning
of the current period, then adds or subtracts the computed quantities.
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 the calculated Period is taken the same way as in previous case, but
the calculation in the current Period is a little bit different. First you should
note that system takes for only Confirmed moves for the current period. This means
that you should complete the planning and procurement of the current Period before
going to the next one.
When you plan for future Periods:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**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.
Here 'Periods before calculated' designates all periods starting with the current
until the period before the one being calculated.
Remarks:
++++++++
- Remember to make the proceed with the planning of each period in chronological
order, otherwise the numbers will not reflect the reality
- If you planned for future periods and find that real Confirmed Out is larger
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 products, define two
kinds of periods (e.g. Weekly and Monthly) and use them for different
products. Example: If you use always Weekly periods for Product A, and
Monthly periods for Product B all calculations will work correctly. You
can also use different kind of periods for the same product 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 applies to Forecasts lines.
""",
'data': [
'security/stock_planning_security.xml',
'security/ir.model.access.csv',
'stock_planning_view.xml',
'wizard/stock_planning_create_periods_view.xml',
'wizard/stock_planning_forecast_view.xml',
'wizard/stock_planning_createlines_view.xml',
],
'test': ['test/stock_planning.yml'],
'auto_install': False,
'installable': True,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_stock_planning_manager,stock.planning manager,model_stock_planning,stock.group_stock_manager,1,0,0,0
access_stock_planning_user,stock.planning user,model_stock_planning,stock.group_stock_user,1,1,1,1
access_stock_sale_forecast_user,stock.sale.forecast user,model_stock_sale_forecast,base.group_sale_salesman,1,1,1,1
access_stock_sale_forecast_manager,stock.sale.forecast manager,model_stock_sale_forecast,base.group_sale_manager,1,0,0,0
access_stock_period_manager,stock.period manager,model_stock_period,base.group_sale_salesman,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_stock_planning_manager stock.planning manager model_stock_planning stock.group_stock_manager 1 0 0 0
3 access_stock_planning_user stock.planning user model_stock_planning stock.group_stock_user 1 1 1 1
4 access_stock_sale_forecast_user stock.sale.forecast user model_stock_sale_forecast base.group_sale_salesman 1 1 1 1
5 access_stock_sale_forecast_manager stock.sale.forecast manager model_stock_sale_forecast base.group_sale_manager 1 0 0 0
6 access_stock_period_manager stock.period manager model_stock_period base.group_sale_salesman 1 1 1 1

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<!-- multi -->
<record model="ir.rule" id="stock_planning_comp_rule">
<field name="name">stock_planning multi-company</field>
<field name="model_id" ref="model_stock_planning"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record model="ir.rule" id="stock_sale_forecast_comp_rule">
<field name="name">stock_sale_forecast multi-company</field>
<field name="model_id" ref="model_stock_sale_forecast"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
</data>
</openerp>

View File

@ -1,747 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import time
from datetime import datetime
from dateutil.relativedelta import relativedelta
from osv import osv, fields
import netsvc
from tools.translate import _
import logging
import decimal_precision as dp
_logger = logging.getLogger(__name__)
def rounding(fl, round_value):
if not round_value:
return fl
return round(fl / round_value) * round_value
# Periods have no company_id field as they can be shared across similar companies.
# If somone thinks different it can be improved.
class stock_period(osv.osv):
_name = "stock.period"
_description = "stock period"
_order = "date_start"
_columns = {
'name': fields.char('Period Name', size=64, required=True),
'date_start': fields.datetime('Start Date', required=True),
'date_stop': fields.datetime('End Date', required=True),
'state': fields.selection([('draft','Draft'), ('open','Open'),('close','Close')], 'Status'),
}
_defaults = {
'state': 'draft'
}
def button_open(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'open'})
return True
def button_close(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'close'})
return True
stock_period()
# Stock and Sales Forecast object. Previously stock_planning_sale_prevision.
# A lot of changes in 1.1
class stock_sale_forecast(osv.osv):
_name = "stock.sale.forecast"
_columns = {
'company_id':fields.many2one('res.company', 'Company', required=True),
'create_uid': fields.many2one('res.users', 'Responsible'),
'name': fields.char('Name', size=64, readonly=True, states={'draft': [('readonly',False)]}),
'user_id': fields.many2one('res.users', 'Created/Validated by',readonly=True, \
help='Shows who created this forecast, or who validated.'),
'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True, readonly=True, states={'draft': [('readonly',False)]}, \
help='Shows which warehouse this forecast concerns. '\
'If during stock planning you will need sales forecast for all warehouses choose any warehouse now.'),
'period_id': fields.many2one('stock.period', 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}, \
help = 'Shows which period this forecast concerns.'),
'product_id': fields.many2one('product.product', 'Product', readonly=True, required=True, states={'draft':[('readonly',False)]}, \
help = 'Shows which product this forecast concerns.'),
'product_qty': fields.float('Forecast Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, readonly=True, \
states={'draft':[('readonly',False)]}, help= 'Forecast Product quantity.'),
'product_amt': fields.float('Product Amount', readonly=True, states={'draft':[('readonly',False)]}, \
help='Forecast value which will be converted to Product Quantity according to prices.'),
'product_uom_categ': fields.many2one('product.uom.categ', 'Product Unit of Measure Category'), # Invisible field for product_uom domain
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, readonly=True, states={'draft':[('readonly',False)]}, \
help = "Unit of Measure used to show the quantities 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 UoS Category'), # Invisible field for product_uos domain
# Field used in onchange_uom to check what uom was before change and recalculate quantities according to old uom (active_uom) and new uom.
'active_uom': fields.many2one('product.uom', string = "Active Unit of Measure"),
'state': fields.selection([('draft','Draft'),('validated','Validated')],'Status',readonly=True),
'analyzed_period1_id': fields.many2one('stock.period', 'Period1', readonly=True, states={'draft':[('readonly',False)]},),
'analyzed_period2_id': fields.many2one('stock.period', 'Period2', readonly=True, states={'draft':[('readonly',False)]},),
'analyzed_period3_id': fields.many2one('stock.period', 'Period3', readonly=True, states={'draft':[('readonly',False)]},),
'analyzed_period4_id': fields.many2one('stock.period', 'Period4', readonly=True, states={'draft':[('readonly',False)]},),
'analyzed_period5_id': fields.many2one('stock.period' , 'Period5', readonly=True, states={'draft':[('readonly',False)]},),
'analyzed_user_id': fields.many2one('res.users', 'This User', required=False, readonly=True, states={'draft':[('readonly',False)]},),
'analyzed_team_id': fields.many2one('crm.case.section', 'Sales Team', required=False, \
readonly=True, states={'draft':[('readonly',False)]},),
'analyzed_warehouse_id': fields.many2one('stock.warehouse' , 'This Warehouse', required=False, \
readonly=True, states={'draft':[('readonly',False)]}),
'analyze_company': fields.boolean('Per Company', readonly=True, states={'draft':[('readonly',False)]}, \
help = "Check this box to see the sales for whole company."),
'analyzed_period1_per_user': fields.float('This User Period1', readonly=True),
'analyzed_period2_per_user': fields.float('This User Period2', readonly=True),
'analyzed_period3_per_user': fields.float('This User Period3', readonly=True),
'analyzed_period4_per_user': fields.float('This User Period4', readonly=True),
'analyzed_period5_per_user': fields.float('This User Period5', readonly=True),
'analyzed_period1_per_dept': fields.float('This Dept Period1', readonly=True),
'analyzed_period2_per_dept': fields.float('This Dept Period2', readonly=True),
'analyzed_period3_per_dept': fields.float('This Dept Period3', readonly=True),
'analyzed_period4_per_dept': fields.float('This Dept Period4', readonly=True),
'analyzed_period5_per_dept': fields.float('This Dept Period5', readonly=True),
'analyzed_period1_per_warehouse': fields.float('This Warehouse Period1', readonly=True),
'analyzed_period2_per_warehouse': fields.float('This Warehouse Period2', readonly=True),
'analyzed_period3_per_warehouse': fields.float('This Warehouse Period3', readonly=True),
'analyzed_period4_per_warehouse': fields.float('This Warehouse Period4', readonly=True),
'analyzed_period5_per_warehouse': fields.float('This Warehouse Period5', readonly=True),
'analyzed_period1_per_company': fields.float('This Company Period1', readonly=True),
'analyzed_period2_per_company': fields.float('This Company Period2', readonly=True),
'analyzed_period3_per_company': fields.float('This Company Period3', readonly=True),
'analyzed_period4_per_company': fields.float('This Company Period4', readonly=True),
'analyzed_period5_per_company': fields.float('This Company Period5', readonly=True),
}
_defaults = {
'user_id': lambda obj, cr, uid, context: uid,
'state': 'draft',
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.sale.forecast', context=c),
}
def action_validate(self, cr, uid, ids, *args):
self.write(cr, uid, ids, {'state': 'validated','user_id': uid})
return True
def unlink(self, cr, uid, ids, context=None):
forecasts = self.read(cr, uid, ids, ['state'])
unlink_ids = []
for t in forecasts:
if t['state'] in ('draft'):
unlink_ids.append(t['id'])
else:
raise osv.except_osv(_('Invalid Action!'), _('Cannot delete a validated sales forecast.'))
osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
return True
def onchange_company(self, cr, uid, ids, company_id=False):
result = {}
if not company_id:
return result
result['warehouse_id'] = False
result['analyzed_user_id'] = False
result['analyzed_team_id'] = False
result['analyzed_warehouse_id'] = False
return {'value': result}
def product_id_change(self, cr, uid, ids, product_id=False):
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['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
ret['active_uom'] = product_rec.uom_id.id
else:
ret['product_uom'] = False
ret['product_uom_categ'] = False
ret['product_uos_categ'] = False
res = {'value': ret}
return res
def onchange_uom(self, cr, uid, ids, product_uom=False, product_qty=0.0,
active_uom=False, product_id=False):
ret = {}
if product_uom and product_id:
coeff_uom2def = self._to_default_uom_factor(cr, uid, product_id, active_uom, {})
coeff_def2uom, round_value = self._from_default_uom_factor(cr, uid, product_id, product_uom, {})
coeff = coeff_uom2def * coeff_def2uom
ret['product_qty'] = rounding(coeff * product_qty, round_value)
ret['active_uom'] = product_uom
return {'value': ret}
def product_amt_change(self, cr, uid, ids, product_amt=0.0, product_uom=False, product_id=False):
round_value = 1
qty = 0.0
if product_amt and product_id:
product = self.pool.get('product.product').browse(cr, uid, product_id)
coeff_def2uom = 1
if (product_uom != product.uom_id.id):
coeff_def2uom, round_value = self._from_default_uom_factor(cr, uid, product_id, product_uom, {})
qty = rounding(coeff_def2uom * product_amt/(product.product_tmpl_id.list_price), round_value)
res = {'value': {'product_qty': qty}}
return res
def _to_default_uom_factor(self, cr, uid, product_id, uom_id, context=None):
uom_obj = self.pool.get('product.uom')
product_obj = self.pool.get('product.product')
product = product_obj.browse(cr, uid, product_id, context=context)
uom = uom_obj.browse(cr, uid, uom_id, context=context)
coef = uom.factor
if uom.category_id.id <> product.uom_id.category_id.id:
coef = coef * product.uos_coeff
return product.uom_id.factor / coef
def _from_default_uom_factor(self, cr, uid, product_id, uom_id, context=None):
uom_obj = self.pool.get('product.uom')
product_obj = self.pool.get('product.product')
product = product_obj.browse(cr, uid, product_id, context=context)
uom = uom_obj.browse(cr, uid, uom_id, context=context)
res = uom.factor
if uom.category_id.id <> product.uom_id.category_id.id:
res = res * product.uos_coeff
return res / product.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) " ,(tuple(so_line), tuple(so), company, tuple(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)" ,(tuple(so_line), tuple(so), company, tuple(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)", (tuple(so_line), tuple(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, context=context):
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:
shops = users = None
if obj.analyzed_warehouse_id:
shops = self.pool.get('sale.shop').search(cr, uid,[('warehouse_id','=', obj.analyzed_warehouse_id.id)], context = context)
if obj.analyzed_team_id:
users = [u.id for u in obj.analyzed_team_id.member_ids]
factor, _ = self._from_default_uom_factor(cr, uid, obj.product_id.id, 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:
if obj.analyzed_user_id:
sales[i][0] = self._sales_per_users(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, [obj.analyzed_user_id.id])
sales[i][0] *= factor
if users:
sales[i][1] = self._sales_per_users(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, users)
sales[i][1] *= factor
if shops:
sales[i][2] = self._sales_per_warehouse(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, shops)
sales[i][2] *= factor
if obj.analyze_company:
sales[i][3] = self._sales_per_company(cr, uid, so_period_ids, so_line_product_ids, 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()
# 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=None):
if context is None:
context = {}
product_obj = self.pool.get('product.product')
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
prod_id = val.product_id.id
if done:
context.update({ 'states':('done',), 'what':(direction,) })
prod_ids = [prod_id]
st = product_obj.get_product_available(cr, uid, prod_ids, context=context)
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)
return res
def _get_outgoing_before(self, cr, uid, val, date_start, date_stop, context=None):
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_default_uom(cr, uid, val, planning_qtys, context)
return res
def _to_default_uom(self, cr, uid, val, qtys, context=None):
res_qty = 0
if qtys:
for qty, prod_uom in qtys:
coef = self._to_default_uom_factor(cr, uid, val.product_id.id, prod_uom, context=context)
res_qty += qty * coef
return res_qty
def _to_form_uom(self, cr, uid, val, qtys, context=None):
res_qty = 0
if qtys:
for qty, prod_uom in qtys:
coef = self._to_default_uom_factor(cr, uid, val.product_id.id, prod_uom, context=context)
res_coef, round_value = self._from_default_uom_factor(cr, uid, val.product_id.id, 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=None):
res = {}
for val in self.browse(cr, uid, ids, context=context):
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_form_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_form_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=None):
if context is None:
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=None):
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=None):
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.product_id.id, ret[2], context)
res_coef, round_value = self._from_default_uom_factor(cr, uid, val.product_id.id, 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'] = rounding(ret[1]*coef, round_value)
return res
def onchange_company(self, cr, uid, ids, company_id=False):
result = {}
if company_id:
result['warehouse_id'] = False
return {'value': result}
def onchange_uom(self, cr, uid, ids, product_uom=False, product_id=False, active_uom=False,
planned_outgoing=0.0, to_procure=0.0):
ret = {}
if not product_uom:
return {}
if active_uom:
coeff_uom2def = self._to_default_uom_factor(cr, uid, product_id, active_uom, {})
coeff_def2uom, round_value = self._from_default_uom_factor(cr, uid, product_id, product_uom, {})
coeff = coeff_uom2def * coeff_def2uom
ret['planned_outgoing'] = rounding(coeff * planned_outgoing, round_value)
ret['to_procure'] = rounding(coeff * 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')],'Status',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.', select=True),
'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 Unit of Measure Category'), # Invisible field for product_uom domain
'product_uom': fields.many2one('product.uom', 'Unit of Measure', required=True, help = "Unit of Measure used to show the quantities of stock calculation." \
"You can use units from default category or from second category (UoS category)."),
'product_uos_categ': fields.many2one('product.uom.categ', 'Product Unit of Measure Category'), # Invisible field for product_uos domain
# Field used in onchange_uom to check what uom was before change to recalculate quantities according to old uom (active_uom) and new uom.
'active_uom': fields.many2one('product.uom', string = "Active Unit of Measure"), # It works only in Forecast
'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, 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, 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 besides Confirmed Out. 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, type='char', string='Past/Future'),
'minimum_op': fields.function(_get_op, type='float', string = 'Minimum Rule', multi= 'minimum', \
help = 'Minimum quantity set in Minimum Stock Rules for this Warehouse'),
'maximum_op': fields.function(_get_op, type='float', string = 'Maximum Rule', multi= 'maximum', \
help = 'Maximum quantity set in Minimum Stock Rules for this Warehouse'),
'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 = "Check 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 Warehouse'."),
"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 Warehouse' with Supply Warehouse."),
}
_defaults = {
'state': '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, product_id, uom_id, context=None):
uom_obj = self.pool.get('product.uom')
product_obj = self.pool.get('product.product')
product = product_obj.browse(cr, uid, product_id, context=context)
uom = uom_obj.browse(cr, uid, uom_id, context=context)
coef = uom.factor
if uom.category_id.id != product.uom_id.category_id.id:
coef = coef * product.uos_coeff
return product.uom_id.factor / coef
def _from_default_uom_factor(self, cr, uid, product_id, uom_id, context=None):
uom_obj = self.pool.get('product.uom')
product_obj = self.pool.get('product.product')
product = product_obj.browse(cr, uid, product_id, context=context)
uom = uom_obj.browse(cr, uid, uom_id, context=context)
res = uom.factor
if uom.category_id.id != product.uom_id.category_id.id:
res = res * product.uos_coeff
return res / product.uom_id.factor, uom.rounding
def calculate_planning(self, cr, uid, ids, context, *args):
one_second = relativedelta(seconds=1)
today = datetime.today()
current_date_beginning_c = datetime(today.year, today.month, today.day)
current_date_end_c = current_date_beginning_c + relativedelta(days=1, seconds=-1) # to get hour 23:59:59
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')
_logger.debug("Calculate Planning: current date beg: %s and end: %s", current_date_beginning, current_date_end)
for val in self.browse(cr, uid, ids, context=context):
day = datetime.strptime(val.period_id.date_start, '%Y-%m-%d %H:%M:%S')
dbefore = datetime(day.year, day.month, day.day) - one_second
day_before_calculated_period = dbefore.strftime('%Y-%m-%d %H:%M:%S') # one day before start of calculated period
_logger.debug("Day before calculated period: %s ", day_before_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 = datetime.strptime(start_date_current_period, '%Y-%m-%d %H:%M:%S')
dbefore = datetime(day.year, day.month, day.day) - one_second
date_for_start = dbefore.strftime('%Y-%m-%d %H:%M:%S') # one day before current period
_logger.debug("Date for start: %s", date_for_start)
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.product_id.id, 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=None):
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.product_id.id, val.product_uom.id, {})
coeff_def2uom, round_value = self._from_default_uom_factor(cr, uid, val.product_id.id, 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.product_id.id, val.product_uom.id, {})
uos = val.product_id.uos_id.id
coeff_def2uom, round_value = self._from_default_uom_factor(cr, uid, val.product_id.id, 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.product_id.id, 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, context=context):
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=context)
proc_id = self.pool.get('procurement.order').create(cr, uid, {
'company_id' : obj.company_id.id,
'name': _('MPS planning for %s') %(obj.period_id.name),
'origin': _('MPS(%s) %s') %(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 by MPS for user: %s Creation Date: %s \
\n For period: %s \
\n according to state: \
\n Warehouse Forecast: %s \
\n Initial Stock: %s \
\n Planned Out: %s Planned In: %s \
\n Already Out: %s Already In: %s \
\n Confirmed Out: %s Confirmed In: %s \
\n Planned Out Before: %s Confirmed In Before: %s \
\n Expected Out: %s Incoming Left: %s \
\n Stock Simulation: %s Minimum stock: %s') %(user.login, time.strftime('%Y-%m-%d %H:%M:%S'),
obj.period_id.name, obj.warehouse_forecast, obj.planned_outgoing, obj.stock_start, obj.to_procure,
obj.already_out, obj.already_in, obj.outgoing, obj.incoming, obj.outgoing_before, obj.incoming_before,
obj.outgoing_left, obj.incoming_left, obj.stock_simulation, obj.minimum_op)
}, context=context)
wf_service = netsvc.LocalService("workflow")
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
self.calculate_planning(cr, uid, ids, context)
prev_text = obj.history or ""
self.write(cr, uid, ids, {
'history': _('%s Procurement (%s, %s) %s %s \n') % (prev_text, user.login, time.strftime('%Y.%m.%d %H:%M'),
obj.incoming_left, obj.product_uom.name)
})
return True
def internal_supply(self, cr, uid, ids, context, *args):
for obj in self.browse(cr, uid, ids, context=context):
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(%s) %s') %(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: %s Creation Date: %s \
\nFor period: %s according to state: \
\n Warehouse Forecast: %s \
\n Initial Stock: %s \
\n Planned Out: %s Planned In: %s \
\n Already Out: %s Already In: %s \
\n Confirmed Out: %s Confirmed In: %s \
\n Planned Out Before: %s Confirmed In Before: %s \
\n Expected Out: %s Incoming Left: %s \
\n Stock Simulation: %s Minimum stock: %s ')
% (user.login, time.strftime('%Y-%m-%d %H:%M:%S'), obj.period_id.name, obj.warehouse_forecast,
obj.stock_start, obj.planned_outgoing, obj.to_procure, obj.already_out, obj.already_in,
obj.outgoing, obj.incoming, obj.outgoing_before, obj.incoming_before,
obj.outgoing_left, obj.incoming_left, obj.stock_simulation, obj.minimum_op)
})
move_id = self.pool.get('stock.move').create(cr, uid, {
'name': _('MPS(%s) %s') %(user.login, obj.period_id.name),
'picking_id': picking_id,
'product_id': obj.product_id.id,
'date': 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': _('%s Pick List %s (%s, %s) %s %s \n') % (prev_text, pick_name, user.login, time.strftime('%Y.%m.%d %H:%M'),
obj.incoming_left, obj.product_uom.name)
})
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:

View File

@ -1,347 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<menuitem id="menu_stock_period_main"
name="Stock Periods"
parent="stock.menu_stock_configuration" sequence="20"/>
<!-- Periods tree and form section -->
<record id="view_stock_period_form" model="ir.ui.view">
<field name="name">stock.period.form</field>
<field name="model">stock.period</field>
<field name="arch" type="xml">
<form string="Stock Period" version="7.0">
<header>
<button name="button_open" string="Open" states="draft" type="object"/>
<button name="button_close" string="Close" states="open" type="object"/>
<field name="state" widget="statusbar" statusbar_visible="draft,open"/>
</header>
<sheet string="Stock Periods">
<group col="4">
<field name="name"/>
<newline/>
<field name="date_start"/>
<field name="date_stop"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_stock_period_tree" model="ir.ui.view">
<field name="name">stock.period.tree</field>
<field name="model">stock.period</field>
<field name="arch" type="xml">
<tree string="Stock and Sales Period" editable="bottom" colors="gray:state == 'close';black:state in ('draft','open')">
<field name="name"/>
<field name="date_start"/>
<field name="date_stop"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="view_stock_period_search" model="ir.ui.view">
<field name="name">stock.period.search</field>
<field name="model">stock.period</field>
<field name="arch" type="xml">
<search string="Stock Periods">
<field name="name" string="Stock Periods"/>
<field name="date_start"/>
<field name="date_stop"/>
<filter string="Current" domain="[('state','in',['draft','open'])]" icon="terp-check" help="Current Periods"/>
<filter string="Closed" domain="[('state','=','close')]" icon="gtk-stop" help="Closed Periods"/>
</search>
</field>
</record>
<record id="action_stock_period_form" model="ir.actions.act_window">
<field name="name">Stock Periods</field>
<field name="res_model">stock.period</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_stock_period_search"/>
<field name="help">Stock periods are used for stock planning. Stock periods are independent of account periods. You can use wizard for creating periods and review them here.</field>
</record>
<menuitem
id="menu_stock_period"
parent="stock.menu_stock_configuration"
action="action_stock_period_form"
sequence = "25"/>
<record id="view_stock_sale_forecast_form" model="ir.ui.view">
<field name="name">stock.sale.forecast.form</field>
<field name="model">stock.sale.forecast</field>
<field name="arch" type="xml">
<form string="Stock and Sale Forecast" version="7.0">
<header>
<button name="action_validate" string="Approve" states="draft" type="object" class="oe_highlight"/>
<field name="state" widget="statusbar"/>
</header>
<sheet string="Stock and Sales Forecast">
<group colspan = "4" col = "4" class="oe_header">
<group colspan = "2" col="2" >
<field name="company_id"
groups="base.group_multi_company"
widget="selection"
on_change="onchange_company(company_id)"/>
<field name="warehouse_id" domain = "[('company_id','=',company_id)] "/>
<field name="period_id" />
<field name="product_id" on_change="product_id_change(product_id)" />
<field name="product_uom_categ" invisible = "True"/>
<field name="product_uos_categ" invisible = "True"/>
<field name="product_uom"
domain = "[('category_id','in',[product_uom_categ,product_uos_categ])]"
on_change = "onchange_uom(product_uom, product_qty, active_uom, product_id)"/>
<field name="active_uom" invisible = "True" />
</group>
<group colspan = "2" col="2" >
<field name="user_id"/>
<field name="product_amt" on_change="product_amt_change(product_amt, product_uom, product_id)" />
<field name="product_qty" />
</group>
</group>
<separator colspan ="4" string="Sales History"/>
<group colspan = "4" col = "5">
<label string = " "/>
<label string = "Per User :"/>
<label string = "Per Department :"/>
<label string = "Per Warehouse :"/>
<label string = ""/>
<label string = "Periods :"/>
<field name="analyzed_user_id" nolabel = "1" domain = "[('company_id','=',company_id)] "/>
<field name="analyzed_team_id" nolabel = "1" />
<field name="analyzed_warehouse_id" nolabel = "1" domain = "[('company_id','=',company_id)] " />
<group colspan = "1" col = "2">
<field name = "analyze_company"/>
</group>
<field name = "analyzed_period1_id" nolabel = "1" />
<field name = "analyzed_period1_per_user" nolabel = "1" />
<field name = "analyzed_period1_per_dept" nolabel = "1" />
<field name = "analyzed_period1_per_warehouse" nolabel = "1" />
<field name = "analyzed_period1_per_company" nolabel = "1"/>
<field name = "analyzed_period2_id" nolabel = "1" />
<field name = "analyzed_period2_per_user" nolabel = "1" />
<field name = "analyzed_period2_per_dept" nolabel = "1" />
<field name = "analyzed_period2_per_warehouse" nolabel = "1" />
<field name = "analyzed_period2_per_company" nolabel = "1" />
<field name = "analyzed_period3_id" nolabel = "1"/>
<field name = "analyzed_period3_per_user" nolabel = "1" />
<field name = "analyzed_period3_per_dept" nolabel = "1" />
<field name = "analyzed_period3_per_warehouse" nolabel = "1" />
<field name = "analyzed_period3_per_company" nolabel = "1" />
<field name = "analyzed_period4_id" nolabel = "1" />
<field name = "analyzed_period4_per_user" nolabel = "1" />
<field name = "analyzed_period4_per_dept" nolabel = "1" />
<field name = "analyzed_period4_per_warehouse" nolabel = "1" />
<field name = "analyzed_period4_per_company" nolabel = "1" />
<field name = "analyzed_period5_id" nolabel = "1" />
<field name = "analyzed_period5_per_user" nolabel = "1" />
<field name = "analyzed_period5_per_dept" nolabel = "1" />
<field name = "analyzed_period5_per_warehouse" nolabel = "1" />
<field name = "analyzed_period5_per_company" nolabel = "1" />
<button name="calculate_sales_history" icon="gtk-execute"
string="Calculate Sales History" type="object" colspan = "1" states="draft" />
</group>
</sheet>
</form>
</field>
</record>
<record id="view_stock_sale_forecast_tree" model="ir.ui.view">
<field name="name">stock.sale.forecast.tree</field>
<field name="model">stock.sale.forecast</field>
<field name="arch" type="xml">
<tree string="Sales Forecasts" colors="gray:state=='validated';black:state=='draft'">
<field name="period_id"/>
<field name="product_id" on_change="product_id_change(product_id)" />
<field name="product_qty"/>
<field name="product_uom" />
<field name="warehouse_id"/>
<field name="company_id"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="view_stock_sale_forecast_graph" model="ir.ui.view">
<field name="name">stock.sale.forecast.graph</field>
<field name="model">stock.sale.forecast</field>
<field name="arch" type="xml">
<graph string="Stock and Sales Forecast" type = "bar">
<field name="period_id"/>
<field name="product_qty"/>
<field name="product_id"/>
</graph>
</field>
</record>
<!-- Forecast section -->
<menuitem id="menu_stock_sale_forecast" name="Sales Forecasts"
parent="base.menu_base_partner" sequence="5"/>
<record id="view_stock_sale_forecast_filter" model="ir.ui.view">
<field name="name">stock.sale.forecast.list.select</field>
<field name="model">stock.sale.forecast</field>
<field name="arch" type="xml">
<search string="Search Sales Forecast">
<filter icon="terp-camera_test" string="Validated" domain="[('state','=','validated')]" />
<field name="company_id"/>
<field name="warehouse_id" />
<field name="period_id" />
<field name="product_id"/>
<field name="create_uid"/>
<group expand="0" string="Group By...">
<filter string="Company" icon="terp-go-home" domain="[]" context="{'group_by':'company_id'}"/>
<filter string="Warehouse " icon="terp-go-home" domain="[]" context="{'group_by':'warehouse_id'}"/>
<filter string="Product" icon="terp-accessories-archiver" domain="[]" context="{'group_by':'product_id'}"/>
<filter string='Default Unit of Measure' icon="terp-mrp" domain="[]" context="{'group_by' : 'product_uom'}" />
</group>
</search>
</field>
</record>
<record id="action_view_stock_sale_forecast_form" model="ir.actions.act_window">
<field name="name">Sales Forecast</field>
<field name="res_model">stock.sale.forecast</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,graph</field>
<field name="context">{"search_default_create_uid":uid}</field>
<field name="help">This quantity sales forecast is an indication for Stock Planner to make procurement manually or to complement automatic procurement. You can use manual procurement with this forecast when some periods are exceptional for usual minimum stock rules.</field>
</record>
<menuitem id="menu_stock_sale_forecast_all"
name="Sales Forecasts"
parent="menu_stock_sale_forecast"
action="action_view_stock_sale_forecast_form"/>
<!-- Planning section -->
<menuitem id="menu_stock_planning_manual" name="Master Procurement Schedule" parent="procurement.menu_stock_sched" sequence="10"/>
<record id="view_stock_planning_form" model="ir.ui.view">
<field name="name">stock.planning.form</field>
<field name="model">stock.planning</field>
<field name="priority">50</field>
<field name="arch" type="xml">
<form string="Stock Planning" version="7.0">
<group>
<group string="General Info">
<field name="company_id" widget="selection"
on_change="onchange_company(company_id)" groups="base.group_multi_company"/>
<field name="warehouse_id" domain = "[('company_id','=',company_id)] " />
<field name="period_id" />
<field name="product_id" on_change="product_id_change(product_id)" />
<field name="product_uom_categ" invisible = "True"/>
<field name="product_uos_categ" invisible = "True"/>
<field name="product_uom"
domain = "[('category_id','in',[product_uom_categ,product_uos_categ])]"
on_change = "onchange_uom(product_uom, product_id, active_uom, planned_outgoing, to_procure)"/>
<field name="active_uom" invisible = "True"/>
<separator string="Current Period Situation" colspan="2"/>
<field name="stock_start"/>
<field name="already_out"/>
<field name="already_in"/>
<separator string="Minimum Stock Rule Indicators" colspan="2"/>
<field name="minimum_op"/>
<field name="maximum_op"/>
<separator string="Forecasts" colspan="2"/>
<field name="warehouse_forecast"/>
<field name="company_forecast"/>
<field name="confirmed_forecasts_only"/>
</group>
<group>
<button name="calculate_planning" string="Compute Planning" type="object" icon="gtk-execute"/>
<separator string="Planning and Situation for Calculated Period" colspan="2"/>
<field name="planned_outgoing"/>
<field name="outgoing"/>
<field name="outgoing_left"/>
<field name="to_procure"/>
<field name="incoming"/>
<label for="incoming_left"/>
<div>
<field name="incoming_left" class="oe_inline"/>
<button name="procure_incomming_left" icon="gtk-convert" string="Make Procurement" type="object" class="oe_inline"/>
<button name="internal_supply" string="Make Picking" help="Supply from Another Warehouse" icon="gtk-convert" type="object" class="oe_inline"/>
</div>
<field name="stock_only"/>
<separator string="Calculated Period Simulation" colspan="2"/>
<field name="outgoing_before"/>
<field name="incoming_before"/>
<field name="stock_simulation"/>
<field name="procure_to_stock"/>
<separator string="Internal Supply" colspan="2"/>
<field name="supply_warehouse_id" domain = "[('company_id','=',company_id)] "/>
<field name="stock_supply_location"/>
</group>
</group>
<group string="Procurement History" colspan="4">
<field name="history" nolabel="1" colspan="4"/>
</group>
</form>
</field>
</record>
<record id="view_stock_planning_filter" model="ir.ui.view">
<field name="name">stock.planning.list.select</field>
<field name="model">stock.planning</field>
<field name="arch" type="xml">
<search string="Search Stock Planning">
<filter icon="terp-purchase" string="No Requisition" domain="[('history','=',False)]" />
<field name="company_id"/>
<field name="warehouse_id"/>
<field name="period_id"/>
<field name="product_id"/>
<group expand="0" string="Group By...">
<filter string="Company" icon="terp-go-home" domain="[]" context="{'group_by':'company_id'}"/>
<filter string="Warehouse " icon="terp-go-home" domain="[]" context="{'group_by':'warehouse_id'}"/>
<filter string="Product" icon="terp-accessories-archiver" domain="[]" context="{'group_by':'product_id'}"/>
</group>
</search>
</field>
</record>
<record id="view_stock_planning_tree" model="ir.ui.view">
<field name="name">stock.planning.tree</field>
<field name="model">stock.planning</field>
<field name="arch" type="xml">
<tree string="Master Procurement Schedule" colors ="blue:line_time=='Past';black:line_time=='Future'">
<field name="period_id"/>
<field name="company_id" invisible="1"/>
<field name="product_id" on_change="product_id_change(product_id)" />
<field name="product_uom"/>
<field name="warehouse_forecast" string="Forecast"/>
<field name="planned_outgoing"/>
<field name="to_procure"/>
<field name="line_time" invisible="1"/>
<field name="stock_simulation" string="Stock"/> <!-- previously stock_start -->
<field name="warehouse_id" domain = "[('company_id','=',company_id)] " />
</tree>
</field>
</record>
<record id="action_view_stock_planning_form" model="ir.actions.act_window">
<field name="name">Master Procurement Schedule</field>
<field name="res_model">stock.planning</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help">The Master Procurement Schedule can be the main driver for warehouse replenishment, or can complement the automatic MRP scheduling (minimum stock rules, etc.).
Each MPS line gives you a pre-computed overview of the incoming and outgoing quantities of a given product for a given Stock Period in a given Warehouse, based on the current and future stock levels,
as well as the planned stock moves. The forecast quantities can be altered manually, and when satisfied with resulting (simulated) Stock quantity, you can trigger the procurement of what is missing to reach your desired quantities</field>
</record>
<menuitem
id="menu_stock_planning"
parent="menu_stock_planning_manual"
action="action_view_stock_planning_form"/>
</data>
</openerp>

View File

@ -1,257 +0,0 @@
-
In order to test the flow of stock planning module. I will create forecast periods,
stock forecast and then with master procurement schedule.
-
I create weekly stock periods for the month of July.
-
I create stock period for the first week of July.
-
!record {model: stock.period, id: stock_period_01}:
date_start: !eval "'%s-07-01 00:00:00' %(datetime.now().year)"
date_stop: !eval "'%s-07-06 23:59:00' %(datetime.now().year)"
name: !eval "'%s, week 27' %(datetime.now().year)"
-
I create stock period for the second week of July.
-
!record {model: stock.period, id: stock_period_02}:
date_start: !eval "'%s-07-07 00:00:00' %(datetime.now().year)"
date_stop: !eval "'%s-07-12 23:59:00' %(datetime.now().year)"
name: !eval "'%s, week 28' %(datetime.now().year)"
-
I create stock period for the third week of July.
-
!record {model: stock.period, id: stock_period_03}:
date_start: !eval "'%s-07-15 00:00:00' %(datetime.now().year)"
date_stop: !eval "'%s-07-20 23:59:00' %(datetime.now().year)"
name: !eval "'%s, week 29' %(datetime.now().year)"
-
I create stock period for the fourth week of July.
-
!record {model: stock.period, id: stock_period_04}:
date_start: !eval "'%s-07-22 00:00:00' %(datetime.now().year)"
date_stop: !eval "'%s-07-27 23:59:00' %(datetime.now().year)"
name: !eval "'%s, week 30' %(datetime.now().year)"
-
I create stock period for the fifth week of July.
-
!record {model: stock.period, id: stock_period_05}:
date_start: !eval "'%s-07-29 00:00:00' %(datetime.now().year)"
date_stop: !eval "'%s-07-31 23:59:00' %(datetime.now().year)"
name: !eval "'%s, week 31' %(datetime.now().year)"
-
I validate in open state module stock.period
-
!python {model: stock.period}: |
self.button_open(cr, uid, [ref("stock_period_01")], {"lang": 'en_GB', "tz": False, "active_model": 'ir.ui.menu', "active_id": ref("stock_planning.menu_stock_period_main"), "active_ids": [ref("stock_planning.menu_stock_period_main")], "department_id": False})
-
I validate in close state module stock.period
-
!python {model: stock.period}: |
self.button_close(cr, uid, [ref("stock_period_01")], {"lang": 'en_GB', "tz": False, "active_model": 'ir.ui.menu', "active_id": ref("stock_planning.menu_stock_period_main"), "active_ids": [ref("stock_planning.menu_stock_period_main")], "department_id": False})
-
Now I create the forecast for this period for all PCs.
-
!record {model: stock.sale.forecast.createlines, id: stock_sale_forecast_createlines_0}:
company_id: base.main_company
period_id: stock_period_03
product_categ_id: product.product_category_4
warehouse_id: stock.warehouse0
-
Performing an osv_memory action create_forecast on module stock.sale.forecast.createlines
-
!python {model: stock.sale.forecast.createlines}: |
self.create_forecast(cr, uid, [ref("stock_sale_forecast_createlines_0")], {"lang":
'en_US', "active_model": "ir.ui.menu", "active_ids": [ref("stock_planning.menu_stock_sale_forecast_createlines")],
"tz": False, "active_id": ref("stock_planning.menu_stock_sale_forecast_createlines"),
})
-
I create stock.period.createlines for the first week of August.
-
!record {model: stock.period.createlines, id: stock_period_createlines_week01}:
date_start: !eval "'%s-08-01' %(datetime.now().year)"
date_stop: !eval "'%s-08-06' %(datetime.now().year)"
name: !eval "'%s, week 31' %(datetime.now().year)"
-
Performing an osv_memory action create_stock_periods on module stock.period.createlines
-
!python {model: stock.period.createlines}: |
self.create_stock_periods(cr, uid, [ref("stock_period_createlines_week01")], {"lang":
'en_US', "name": 'Weekly', "interval": 6, "active_model": "ir.ui.menu", "active_ids": [ref("stock_planning.menu_stock_period_creatlines")],
"tz": False, "active_id": ref("stock_planning.menu_stock_period_creatlines"),
})
-
I create stock.period.createlines for the month of August - September
-
!record {model: stock.period.createlines, id: stock_period_createlines_month01}:
date_start: !eval "'%s-08-01' %(datetime.now().year)"
date_stop: !eval "'%s-09-01' %(datetime.now().year)"
name: !eval "'%s, month 8' %(datetime.now().year)"
-
Performing an osv_memory action create_stock_periods on module stock.period.createlines
-
!python {model: stock.period.createlines}: |
self.create_stock_periods(cr, uid, [ref("stock_period_createlines_month01")], {"lang":
'en_US', "name": 'Monthly', "interval": 6, "active_model": "ir.ui.menu", "active_ids": [ref("stock_planning.menu_stock_period_creatlines")],
"tz": False, "active_id": ref("stock_planning.menu_stock_period_creatlines"),
})
-
I create stock.period.createlines for the Day of August
-
!record {model: stock.period.createlines, id: stock_period_createlines_day01}:
date_start: !eval "'%s-08-01' %(datetime.now().year)"
date_stop: !eval "'%s-08-01' %(datetime.now().year)"
name: !eval "'%s, day 01' %(datetime.now().year)"
-
Performing an osv_memory action create_stock_periods on module stock.period.createlines
-
!python {model: stock.period.createlines}: |
self.create_stock_periods(cr, uid, [ref("stock_period_createlines_day01")], {"lang":
'en_US', "name": 'Daily', "interval": 6, "active_model": "ir.ui.menu", "active_ids": [ref("stock_planning.menu_stock_period_creatlines")],
"tz": False, "active_id": ref("stock_planning.menu_stock_period_creatlines"),
})
-
I Read date_start on module stock.period.createlines.
-
!python {model: stock.period.createlines }: |
self._get_new_period_start(cr, uid,{"lang":'en_US', "active_model": "ir.ui.menu",
"active_ids": [ref("stock_planning.menu_stock_period_creatlines")],
"tz": False, "active_id": ref("stock_planning.menu_stock_period_creatlines"),"department_id": False
})
-
I create a sale order for PC and Laptop.
-
!record {model: sale.order, id: sale_order_so0}:
date_order: !eval time.strftime('%Y-07-20')
invoice_quantity: order
name: TESTSO006
order_line:
- name: '[PC200] PC Assemble + 2GB RAM'
price_unit: 750.0
product_uom: product.product_uom_unit
product_uom_qty: 5.0
state: draft
delay: 2.0
product_id: product.product_product_4
product_uos_qty: 5.0
th_weight: 0.0
type: make_to_stock
- name: '[LAP-E5] Laptop E5023'
price_unit: 2950.0
product_uom: product.product_uom_unit
product_uom_qty: 12.0
state: draft
delay: 3.0
product_id: product.product_product_25
product_uos_qty: 12.0
th_weight: 0.0
type: make_to_stock
order_policy: manual
partner_id: base.res_partner_7
partner_invoice_id: base.res_partner_address_13
partner_shipping_id: base.res_partner_address_13
picking_policy: direct
pricelist_id: product.list0
shop_id: sale.sale_shop_1
-
Now I want to calculate sales history for both the products PC and Laptop and forecast the quantity.
-
!python {model: stock.sale.forecast}: |
forecast_ids = self.search(cr, uid, [('product_id','=',ref('product.product_product_4'))])
write_dict = {'product_qty': 30.0,
'analyzed_period1_id': ref("stock_period_03"),
'analyzed_user_id': ref("base.user_root"),
'analyzed_warehouse_id': ref("stock.warehouse0")}
context = {"lang": "en_US", "tz":False, "search_default_create_uid": 1, "active_model": "ir.ui.menu",
"department_id": False, "active_ids": [ref("stock_planning.menu_stock_sale_forecast_all")],
"active_id": ref("stock_planning.menu_stock_sale_forecast_all"), }
self.write(cr, uid, forecast_ids, write_dict)
self.calculate_sales_history(cr, uid, forecast_ids, context)
self.action_validate(cr, uid, forecast_ids, context)
-
Now I applying onchange and unlink forecast record for the product PC.
-
!python {model: stock.sale.forecast}: |
forecast_ids = self.search(cr, uid, [('product_id','=',ref('product.product_product_5'))])
self.unlink(cr, uid, forecast_ids, {"lang": "en_US", "tz":
False, "search_default_create_uid": 1, "active_model": "ir.ui.menu", "department_id":
False, "active_ids": [ref("stock_planning.menu_stock_sale_forecast_all")], "active_id":
ref("stock_planning.menu_stock_sale_forecast_all"), })
self.product_id_change(cr, uid, forecast_ids, ref('product.product_product_20'))
self.onchange_uom(cr, uid, forecast_ids, ref('product.product_uom_unit'), False, ref('product.product_uom_unit'), ref('product.product_product_20'))
self.product_amt_change(cr, uid, forecast_ids, 5.0, ref('product.product_uom_unit'), ref('product.product_product_20'))
self.product_amt_change(cr, uid, forecast_ids, 5.0, ref('product.product_uom_unit'), ref('product.product_product_20'))
self._to_default_uom_factor(cr, uid, ref('product.product_product_20'), ref('product.product_uom_unit'), {})
self.onchange_company(cr, uid, forecast_ids, ref('base.res_partner_2'))
-
I create Master procurement schedule for the third week of July.
-
!record {model: stock.planning.createlines, id: stock_planning_createlines_0}:
company_id: base.main_company
period_id: stock_period_03
product_categ_id: product.product_category_4
warehouse_id: stock.warehouse0
-
I create source warehouse for supply from another warehouse.
-
!record {model: stock.warehouse, id: stock_warehouse_0}:
name: shop1
company_id: base.main_company
lot_input_id: stock.stock_location_stock
lot_stock_id: stock.stock_location_stock
lot_output_id: stock.stock_location_output
-
Performing an osv_memory action create_planning on module stock.planning.createlines
-
!python {model: stock.planning.createlines}: |
self.onchange_company(cr, uid, ref("stock_planning_createlines_0"), ref('base.res_partner_2'))
self.create_planning(cr, uid, [ref("stock_planning_createlines_0")], {"lang":
"en_US", "tz": False, "active_model": "ir.ui.menu", "active_ids": [ref("stock_planning.menu_stock_planning_createlines")],
"active_id": ref("stock_planning.menu_stock_planning_createlines"), "department_id":
False, })
-
I calculate the planning in master procurement schedule.
-
!python {model: stock.planning}: |
planning_ids = self.search(cr, uid, [('product_id','=',ref('product.product_product_3')),('period_id','=',ref('stock_period_03'))])
self.write(cr, uid, planning_ids, {'to_procure': 30.0,'planned_outgoing': 40.0,'supply_warehouse_id': ref("stock_warehouse_0")})
self.onchange_company(cr, uid, planning_ids, ref('base.res_partner_2'))
self.onchange_uom(cr, uid, planning_ids, ref('product.product_uom_unit'), ref('product.product_product_3'),ref('product.product_uom_unit'), False, False)
self.product_id_change(cr, uid, planning_ids, ref('product.product_product_20'))
context = {"lang": "en_US", "tz": False, "active_model": "ir.ui.menu",
"active_ids": [ref("stock_planning.menu_stock_planning")],
"active_id": ref("stock_planning.menu_stock_planning"), "department_id": False,
}
self.calculate_planning(cr, uid, planning_ids, context)
self.internal_supply(cr, uid, planning_ids, context)
-
I create a procurement order for the incoming products left.
-
!python {model: stock.planning}: |
planning_ids = self.search(cr, uid, [('product_id','=',ref('product.product_product_3')),('period_id','=',ref('stock_period_03'))])
plann = self.browse(cr, uid, planning_ids)
assert plann[0].incoming_left, 'Incoming Left must be greater than 0 !'
self.procure_incomming_left(cr, uid, planning_ids, {"lang": 'en_US', "tz":
False, "active_model": "ir.ui.menu", "active_ids": [ref("stock_planning.menu_stock_planning")],
"active_id": ref("stock_planning.menu_stock_planning"), "department_id": False,
})
-
I check whether the procurement orders are created or not.
-
!python {model: procurement.order}: |
import datetime
proc_ids = self.search(cr, uid, [('origin','=','MPS(admin) %s, week 29' % (datetime.datetime.now().year)),('product_id','=',ref("product.product_product_3"))])
assert proc_ids,'No Procurements!'

View File

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import stock_planning_create_periods
import stock_planning_forecast
import stock_planning_createlines
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,115 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from datetime import datetime
from dateutil.relativedelta import relativedelta
from osv import osv, fields
# 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=None):
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 = datetime.strptime(last_date,"%Y-%m-%d %H:%M:%S")+ relativedelta(days=1)
period_start = period_start - relativedelta(hours=period_start.hour, minutes=period_start.minute, seconds=period_start.second)
else:
period_start = 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, help="Starting date for planning period."),
'date_stop': fields.date('End Date', required=True, help="Ending date for planning period."),
'period_ids': fields.one2many('stock.period', 'planning_id', 'Periods'),
'period_ids': fields.many2many('stock.period', 'stock_period_createlines_stock_period_rel', 'wizard_id', 'period_id', 'Periods'),
}
_defaults={
'date_start': _get_new_period_start,
}
def create_stock_periods(self, cr, uid, ids, context=None):
interval = context.get('interval',0)
name = context.get('name','Daily')
period_obj = self.pool.get('stock.period')
lines = []
for p in self.browse(cr, uid, ids, context=context):
dt_stp = datetime.strptime(p.date_stop, '%Y-%m-%d')
ds = datetime.strptime(p.date_start, '%Y-%m-%d')
while ds <= dt_stp:
if name =='Daily':
de = ds + relativedelta(days=(interval + 1), seconds =-1)
new_id = period_obj.create(cr, uid, {
'name': de.strftime('%Y-%m-%d'),
'date_start': ds.strftime('%Y-%m-%d %H:%M:%S'),
'date_stop': de.strftime('%Y-%m-%d %H:%M:%S'),
})
ds = ds + relativedelta(days=(interval + 1))
if name =="Weekly":
de = ds + relativedelta(days=(interval + 1), seconds =-1)
if dt_stp < de:
de = dt_stp + relativedelta(days=1, seconds =-1)
else:
de = ds + relativedelta(days=(interval + 1), seconds =-1)
new_name = ds.strftime('Week %W-%Y')
if ds.strftime('%Y') != de.strftime('%Y'):
new_name = ds.strftime('Week %W-%Y') + ', ' + de.strftime('Week %W-%Y')
new_id = period_obj.create(cr, uid, {
'name': new_name,
'date_start': ds.strftime('%Y-%m-%d %H:%M:%S'),
'date_stop': de.strftime('%Y-%m-%d %H:%M:%S'),
})
ds = ds + relativedelta(days=(interval + 1))
if name == "Monthly":
de = ds + relativedelta(months=interval, seconds=-1)
if dt_stp < de:
de = dt_stp + relativedelta(days=1, seconds =-1)
else:
de = ds + relativedelta(months=interval, seconds=-1)
new_name = ds.strftime('%Y/%m')
if ds.strftime('%m') != de.strftime('%m'):
new_name = ds.strftime('%Y/%m') + '-' + de.strftime('%Y/%m')
new_id =period_obj.create(cr, uid, {
'name': new_name,
'date_start': ds.strftime('%Y-%m-%d %H:%M:%S'),
'date_stop': de.strftime('%Y-%m-%d %H:%M:%S'),
})
ds = ds + relativedelta(months=interval)
lines.append(new_id)
return {
'domain': "[('id','in', ["+','.join(map(str, lines))+"])]",
'view_type': 'form',
"view_mode": 'tree,form',
'res_model': 'stock.period',
'type': 'ir.actions.act_window',
}
stock_period_createlines()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Creating periods section -->
<record id="view_stock_period_createlines_form" model="ir.ui.view">
<field name="name">stock.period.createlines.form</field>
<field name="model">stock.period.createlines</field>
<field name="arch" type="xml">
<form string="Stock Periods" version="7.0">
<group col="4">
<label for="date_start" string="Duration"/>
<div>
<field name="date_start"/>
<label string="-"/>
<field name="date_stop"/>
</div>
</group>
<footer>
<button name="create_stock_periods" string="Create Daily Periods" type="object" class="oe_highlight"/>
or
<button name="create_stock_periods" string="Create Weekly Periods" type="object" context="{'interval': 6, 'name': 'Weekly'}" class="oe_highlight"/>
or
<button name="create_stock_periods" string="Create Monthly Periods" type="object" context="{'interval': 1, 'name': 'Monthly'}" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_stock_period_createlines_form" model="ir.actions.act_window">
<field name="name">Stock Periods</field>
<field name="res_model">stock.period.createlines</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="view_stock_period_createlines_form"/>
<field name="help">This wizard helps with the creation of stock planning periods. These periods are independent of financial periods. If you need periods other than day-, week- or month-based, you may also add then manually.</field>
<field name="target">new</field>
</record>
<menuitem id="menu_stock_period_creatlines"
name="Create Stock Periods"
parent="stock.menu_stock_configuration"
action="action_stock_period_createlines_form"
sequence = "20"/>
</data>
</openerp>

View File

@ -1,139 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import osv, fields
from tools.translate import _
# 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=False):
result = {}
if company_id:
result['warehouse_id'] = False
return {'value': result}
_columns = {
'company_id': fields.many2one('res.company', 'Company', required=True),
'period_id': fields.many2one('stock.period' , 'Period', required=True, help = 'Period which planning will concern.'),
'warehouse_id': fields.many2one('stock.warehouse' , 'Warehouse', required=True, help = 'Warehouse which planning will concern.'),
'product_categ_id': 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=None):
if context is None:
context = {}
product_obj = self.pool.get('product.product')
planning_obj = self.pool.get('stock.planning')
mod_obj = self.pool.get('ir.model.data')
prod_categ_obj = self.pool.get('product.category')
planning_lines = []
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_id.id, f.warehouse_id.id))
products_id1 = [x for x, in cr.fetchall()]
else:
categ_ids = f.product_categ_id.id and [f.product_categ_id.id] or []
prod_categ_ids = prod_categ_obj.search(cr,uid,[('parent_id','child_of',categ_ids)])
products_id1 = product_obj.search(cr,uid,[('categ_id','in',prod_categ_ids)])
if len(products_id1)==0:
raise osv.except_osv(_('Error!'), _('No forecasts for selected period or no products available 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_id.id),
('warehouse_id','=',f.warehouse_id.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_id.id, p.id, f.period_id.date_stop) )
ret=cr.fetchone()
if ret:
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_lines.append(planning_obj.create(cr, uid, {
'company_id' : f.warehouse_id.company_id.id,
'period_id': f.period_id.id,
'warehouse_id' : f.warehouse_id.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 {
'domain': "[('id','in', ["+','.join(map(str, planning_lines))+"])]",
'view_type': 'form',
"view_mode": 'tree,form',
'res_model': 'stock.planning',
'type': 'ir.actions.act_window',
}
stock_planning_createlines()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Create Planning section -->
<record id="view_stock_planning_createlines_form" model="ir.ui.view">
<field name="name">stock.planning.createlines.form</field>
<field name="model">stock.planning.createlines</field>
<field name="arch" type="xml">
<form string="Create Stock Planning Lines" version="7.0">
<group col="4">
<field name="period_id"/>
<field name="company_id" widget= "selection" on_change="onchange_company(company_id)" groups="base.group_multi_company"/>
<field name="warehouse_id" domain = "[('company_id','=',company_id)] "/>
<field name="forecasted_products"/>
<field name="product_categ_id" attrs="{'required':[('forecasted_products','=',0)]}"/>
</group>
<footer>
<button name="create_planning" string="Create" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_stock_planning_createlines_form" model="ir.actions.act_window">
<field name="name">Create Stock Planning Lines</field>
<field name="res_model">stock.planning.createlines</field>
<field name="src_model">stock.planning</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="view_stock_planning_createlines_form"/>
<field name="help">This wizard helps create MPS planning lines for a given selected period and warehouse, so you don't have to create them one by one. The wizard doesn't duplicate lines if they already exist for this selection.</field>
<field name="target">new</field>
</record>
<menuitem
id="menu_stock_planning_createlines"
parent="menu_stock_planning_manual"
action="action_stock_planning_createlines_form" sequence="5"/>
</data>
</openerp>

View File

@ -1,108 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import osv, fields
from tools.translate import _
# Creates forecasts records for products from selected Product Category for selected 'Warehouse - Period'
# Object added by contributor in ver 1.1
class stock_sale_forecast_createlines(osv.osv_memory):
_name = "stock.sale.forecast.createlines"
_description = "stock.sale.forecast.createlines"
_columns = {
'company_id': fields.many2one('res.company', 'Company', required=True, select=1),
'warehouse_id': fields.many2one('stock.warehouse' , 'Warehouse', required=True, \
help='Warehouse which forecasts will concern. '\
'If during stock planning you will need sales forecast for all warehouses choose any warehouse now.'),
'period_id': fields.many2one('stock.period', 'Period', required=True, help='Period which forecasts will concern.'),
'product_categ_id': fields.many2one('product.category' , 'Product Category', required=True, \
help ='Product Category of products which created forecasts will concern.'),
'copy_forecast': fields.boolean('Copy Last Forecast', help="Copy quantities from last Stock and Sale Forecast."),
}
_defaults = {
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.sale.forecast.createlines', context=c),
}
def create_forecast(self, cr, uid, ids, context=None):
product_obj = self.pool.get('product.product')
forecast_obj = self.pool.get('stock.sale.forecast')
mod_obj = self.pool.get('ir.model.data')
prod_categ_obj = self.pool.get('product.category')
forecast_lines = []
for f in self.browse(cr, uid, ids, context=context):
categ_ids = f.product_categ_id.id and [f.product_categ_id.id] or []
prod_categ_ids = prod_categ_obj.search(cr, uid, [('parent_id','child_of', categ_ids)])
products_ids = product_obj.search(cr, uid, [('categ_id','in',prod_categ_ids)])
if len(products_ids) == 0:
raise osv.except_osv(_('Error!'), _('No products available in selected category !'))
copy = f.copy_forecast
for p in product_obj.browse(cr, uid, products_ids,{}):
if len(forecast_obj.search(cr, uid, [('product_id','=',p.id) , \
('period_id','=',f.period_id.id), \
('user_id','=',uid), \
('warehouse_id','=',f.warehouse_id.id)]))== 0:
forecast_qty = 0.0
prod_uom = False
if copy:
cr.execute("SELECT period.date_stop, forecast.product_qty, forecast.product_uom \
FROM stock_sale_forecast AS forecast \
LEFT JOIN stock_period AS period \
ON forecast.period_id = period.id \
WHERE (forecast.user_id = %s OR forecast.create_uid = %s OR forecast.write_uid = %s) \
AND forecast.warehouse_id = %s AND forecast.product_id = %s \
AND period.date_stop < %s \
ORDER BY period.date_stop DESC",
(uid, uid, uid, f.warehouse_id.id, p.id, f.period_id.date_stop) )
ret = cr.fetchone()
if ret:
forecast_qty = ret[1]
prod_uom = ret[2]
prod_uom = prod_uom or p.uom_id.id
prod_uos_categ = False
if p.uos_id:
prod_uos_categ = p.uos_id.category_id.id
forecast_lines.append(forecast_obj.create(cr, uid, {
'company_id': f.warehouse_id.company_id.id,
'period_id': f.period_id.id,
'warehouse_id': f.warehouse_id.id,
'product_id': p.id,
'product_qty': forecast_qty,
'product_amt': 0.0,
'product_uom': prod_uom,
'active_uom': prod_uom,
'product_uom_categ': p.uom_id.category_id.id,
'product_uos_categ': prod_uos_categ,
}))
return {
'domain': "[('id','in', ["+','.join(map(str, forecast_lines))+"])]",
'view_type': 'form',
"view_mode": 'tree,form',
'res_model': 'stock.sale.forecast',
'type': 'ir.actions.act_window',
}
stock_sale_forecast_createlines()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Create Forecast section -->
<record id="view_stock_sale_forecast_createlines_form" model="ir.ui.view">
<field name="name">stock.sale.forecast.createlines</field>
<field name="model">stock.sale.forecast.createlines</field>
<field name="arch" type="xml">
<form string="Create Forecasts Lines" version="7.0">
<group col="4">
<field name="period_id"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
<field name="warehouse_id" domain = "[('company_id','=',company_id)] "/>
<field name="copy_forecast"/>
<field name="product_categ_id" widget="selection"/>
</group>
<footer>
<button name="create_forecast" string="Create" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_stock_sale_forecast_createlines_form" model="ir.actions.act_window">
<field name="name">Create Sales Forecasts</field>
<field name="res_model">stock.sale.forecast.createlines</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="view_stock_sale_forecast_createlines_form"/>
<field name="target">new</field>
<field name="help">This wizard helps create many forecast lines at once. After creating them you only have to fill in the forecast quantities. The wizard doesn't duplicate the line when another one exist for the same selection.</field>
</record>
<menuitem id="menu_stock_sale_forecast_createlines" name="Create Sales Forecasts"
parent="menu_stock_sale_forecast" action="action_stock_sale_forecast_createlines_form" sequence="5"/>
</data>
</openerp>