Stock forecast. First version

This commit is contained in:
Bertrand Chenal committed 2008-11-10 21:44:37 +01:00
commit d10382ae39
4 files changed
+460

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of this
#repository contains the full copyright notices and license terms.
from forecast import *
+25
View File
@@ -0,0 +1,25 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
{
'name': 'Stock Forecast',
'version': '0.0.1',
'author': 'B2CK',
'email': 'info@b2ck.com',
'website': 'http://www.tryton.org/',
'description': '''Stock Forecast.
Allow to generate future moves which will enable other process to anticipate them.
''',
'depends': [
'ir',
'workflow',
'stock',
'product',
'company',
],
'xml': [
'forecast.xml',
],
'translation': [
],
}
+241
View File
@@ -0,0 +1,241 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from trytond.osv import fields, OSV
from trytond.wizard import Wizard, WizardOSV
import datetime
from trytond.netsvc import LocalService
STATES = {
'readonly': "state != 'draft'",
}
#TODO : check dates, overlap and max > min
def distribute(delta, qty):
range_delta = range(delta)
a = {}.fromkeys(range_delta, 0)
while qty > 0:
if qty > delta:
for i in range_delta:
a[i] += qty//delta
qty = qty%delta
elif delta//qty > 1:
i = 0
while i < qty:
a[i*delta//qty + (delta//qty/2)] += 1
i += 1
qty = 0
else:
for i in range_delta:
a[i] += 1
qty = delta-qty
i = 0
while i < qty:
a[delta - ((i*delta//qty) + (delta//qty/2)) - 1] -= 1
i += 1
qty = 0
return a
class Forecast(OSV):
"Stock Forecast"
_name = "stock.forecast"
_description = __doc__
_rec_name = 'location'
location = fields.Many2One(
'stock.location', 'Location', required=True,
domain="[('type', '=', 'storage')]", states={
'readonly': "state != 'draft' or bool(lines)",
})
destination = fields.Many2One(
'stock.location', 'Destination', required=True,
domain="[('type', '=', 'customer')]", states=STATES)
from_date = fields.Date('From Date', required=True, states=STATES)
to_date = fields.Date('To Date', required=True, states=STATES)
lines = fields.One2Many(
'stock.forecast.line', 'forecast', 'Lines', states=STATES)
company = fields.Many2One(
'company.company', 'Company', required=True, states={
'readonly': "state != 'draft' or bool(lines)",
})
state = fields.Selection([
('draft', 'Draft'),
('done', 'Done'),
('cancel', 'Cancel'),
], 'State', readonly=True, select=1)
def __init__(self):
super(Forecast, self).__init__()
self._rpc_allowed += [
'button_draft',
]
self._order.insert(0, ('location', 'ASC'))
self._order.insert(0, ('from_date', 'DESC'))
def default_state(self, cursor, user, context=None):
return 'draft'
def default_company(self, cursor, user, context=None):
company_obj = self.pool.get('company.company')
if context is None:
context = {}
if context.get('company'):
return company_obj.name_get(cursor, user, context['company'],
context=context)[0]
return False
def button_draft(self, cursor, user, ids, context=None):
workflow_service = LocalService('workflow')
for forecast in self.browse(cursor, user, ids, context=context):
workflow_service.trg_create(user, self._name, forecast.id, cursor,
context=context)
return True
def set_state_draft(self, cursor, user, forecast_id, context=None):
line_obj = self.pool.get("stock.forecast.line")
forecast = self.browse(cursor, user, forecast_id, context=context)
if forecast.state == "done":
line_obj.cancel_moves(cursor, user, forecast.lines, context=context)
self.write(cursor, user, forecast_id, {
'state': 'draft',
}, context=context)
def set_state_cancel(self, cursor, user, forecast_id, context=None):
self.write(cursor, user, forecast_id, {
'state': 'cancel',
}, context=context)
def set_state_done(self, cursor, user, forecast_id, context=None):
date_obj = self.pool.get('ir.date')
line_obj = self.pool.get('stock.forecast.line')
forecast = self.browse(cursor, user, forecast_id, context=context)
for line in forecast.lines:
line_obj.create_moves(cursor, user, line, context=context)
self.write(
cursor, user, forecast_id, {'state': 'done',}, context=context)
Forecast()
class ForecastLine(OSV):
'Stock Forecast Line'
_name = 'stock.forecast.line'
_description = __doc__
_rec_name = 'product'
product = fields.Many2One('product.product', 'Product', required=True,
domain=[('type', '=', 'stockable')], on_change=['product'])
uom = fields.Function('get_uom', type='many2one', relation='product.uom',
string='UOM')
unit_digits = fields.Function('get_unit_digits', type='integer',
string='Unit Digits')
quantity = fields.Float('Quantity', digits="(16, unit_digits)")
moves = fields.Many2Many(
'stock.move', 'forecast_line_move_rel', 'foreacast_line', 'move','Move',
readonly=True, ondelete_target='CASCADE')
forecast = fields.Many2One('stock.forecast', 'Forecast')
def __init__(self):
super(ForecastLine, self).__init__()
self._sql_constraints += [
('check_line_qty_pos',
'CHECK(quantity >= 0.0)', 'Line quantity must be positive!'),
('forecast_product_uniq', 'UNIQUE(forecast, product)',
'Product must be unique by forcast!'),
]
self._error_messages.update({
'line_quantity': 'Line quantity must be greater than the uom '
'rounding precision.',
})
def default_unit_digits(self, cursor, user, context=None):
return 2
def on_change_product(self, cursor, user, ids, vals, context=None):
product_obj = self.pool.get('product.product')
uom_obj = self.pool.get('product.uom')
res = {}
res['unit_digits'] = 2
if vals.get('product'):
product = product_obj.browse(cursor, user, vals['product'],
context=context)
res['uom'] = uom_obj.name_get(cursor, user, product.default_uom.id,
context=context)[0]
res['unit_digits'] = product.default_uom.digits
return res
def get_uom(self, cursor, user, ids, name, arg, context=None):
uom_obj = self.pool.get('product.uom')
res = {}
for line in self.browse(cursor, user, ids, context=context):
res[line.id] = line.product.default_uom.id
uom2name = {}
for uom_id, name in uom_obj.name_get(cursor, user, res.values(),
context=context):
uom2name[uom_id] = (uom_id, name)
for line_id in res:
res[line_id] = uom2name[res[line_id]]
return res
def get_unit_digits(self, cursor, user, ids, name, arg, context=None):
res = {}
for line in self.browse(cursor, user, ids, context=context):
res[line.id] = line.product.default_uom.digits
return res
def create_moves(self, cursor, user, line, context=None):
move_obj = self.pool.get('stock.move')
uom_obj = self.pool.get('product.uom')
delta = line.forecast.to_date - line.forecast.from_date
delta = delta.days
if line.quantity < line.uom.rounding:
self.raise_user_error(cursor, 'line_quantity')
packet_size = max(line.uom.rounding, 1)
nb_packet = int(line.quantity/packet_size)
distribution = distribute(delta, nb_packet)
unit_price = False
if line.forecast.destination.type == 'customer':
unit_price = line.product.list_price
elif line.forecast.destination.type == 'supplier':
unit_price = line.product.cost_price #XXX use price of first supplier ?
if unit_price:
unit_price = uom_obj.compute_price(
cursor, user, line.product.default_uom, unit_price, line.uom,
context=context)
moves = []
for day, qty in distribution.iteritems():
if qty == 0.0:
continue
mid = move_obj.create(
cursor, user,
{'from_location': line.forecast.location.id,
'to_location': line.forecast.destination.id,
'product': line.product.id,
'uom': line.uom.id,
'quantity': qty * packet_size,
'planned_date': line.forecast.from_date + datetime.timedelta(day),
'company': line.forecast.company.id,
'currency':line.forecast.company.currency,
'unit_price': unit_price,
},
context=context)
moves.append(('add',mid))
self.write(cursor, user, line.id, {'moves': moves}, context=context)
def cancel_moves(self, cursor, user, lines, context=None):
move_obj = self.pool.get('stock.move')
move_obj.write(
cursor, user, [m.id for l in lines for m in l.moves], {'state': 'cancel'},
context=context)
move_obj.delete(
cursor, user, [m.id for l in lines for m in l.moves], context=context)
def copy(self, cursor, user, line_id, default=None, context=None):
default = default and default.copy() or {}
default['forecast']= False
default['move'] = False
return super(ForecastLine, self).copy(cursor, user, line_id,
default=default, context=context)
ForecastLine()
+190
View File
@@ -0,0 +1,190 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="forecast_view_form">
<field name="model">stock.forecast</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="Forecast" col="4">
<label name="location"/>
<field name="location"/>
<label name="destination"/>
<field name="destination"/>
<label name="from_date"/>
<field name="from_date"/>
<label name="to_date"/>
<field name="to_date"/>
<label name="company"/>
<field name="company"/>
<field name="lines" colspan="4"/>
<group col="4" colspan="4">
<label name="state"/>
<field name="state"/>
<group colspan="2" col="3">
<button string="Reset to Draft"
name="button_draft"
type="object"
states="{'invisible': '''state == 'draft' ''', 'readonly': '''%(stock.group_stock_admin)d not in groups'''}"
icon="tryton-clear"/>
<button string="Cancel"
name="cancel"
states="{'invisible': '''state != 'draft' ''', 'readonly': '''%(stock.group_stock_admin)d not in groups'''}"
icon="tryton-cancel"/>
<button string="Confirm"
name="done"
states="{'invisible': '''state != 'draft' ''', 'readonly': '''%(stock.group_stock_admin)d not in groups'''}"
icon="tryton-ok"/>
</group>
</group>
</form>
]]>
</field>
</record>
<record model="ir.ui.view" id="forecast_view_tree">
<field name="model">stock.forecast</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<![CDATA[
<tree string="Forecasts">
<field name="location" select="1"/>
<field name="date" select="1"/>
<field name="state" select="1"/>
<field name="company" select="2"/>
</tree>
]]>
</field>
</record>
<record model="ir.action.act_window" id="act_forecast_form">
<field name="name">Forecasts</field>
<field name="res_model">stock.forecast</field>
<field name="view_type">form</field>
</record>
<record model="ir.action.act_window.view"
id="act_forecast_form_view1">
<field name="sequence" eval="1"/>
<field name="view" ref="forecast_view_tree"/>
<field name="act_window" ref="act_forecast_form"/>
</record>
<record model="ir.action.act_window.view"
id="act_forecast_form_view2">
<field name="sequence" eval="2"/>
<field name="view" ref="forecast_view_form"/>
<field name="act_window" ref="act_forecast_form"/>
</record>
<menuitem parent="stock.menu_stock" sequence="50"
action="act_forecast_form" id="menu_forecast_form"/>
<record model="ir.ui.view" id="forecast_line_view_form">
<field name="model">stock.forecast.line</field>
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="Forecast Line" col="4">
<label name="product"/>
<field name="product"/>
<label name="quantity"/>
<field name="quantity"/>
<label name="uom"/>
<field name="uom"/>
<separator name="moves" colspan="4"/>
<field name="moves" colspan="4" widget="one2many"/>
<label name="forecast"/>
<field name="forecast"/>
<field name="unit_digits" invisible="1" colspan="4"/>
</form>
]]>
</field>
</record>
<record model="ir.ui.view" id="forecast_line_view_tree">
<field name="model">stock.forecast.line</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<![CDATA[
<tree string="Forecast Lines" editable="bottom">
<field name="product" select="1"/>
<field name="quantity" select="2"/>
<field name="uom" select="2"/>
<field name="unit_digits" tree_invisible="1"/>
</tree>
]]>
</field>
</record>
<!-- Workflow forecast -->
<record model="workflow" id="wkf_forecast">
<field name="name">Forecast</field>
<field name="osv">stock.forecast</field>
<field name="on_create">True</field>
</record>
<record model="workflow.activity" id="forecast_act_draft">
<field name="workflow" ref="wkf_forecast"/>
<field name="flow_start">True</field>
<field name="kind">function</field>
<field name="action">set_state_draft()</field>
<field name="name">Draft</field>
</record>
<record model="workflow.activity" id="forecast_act_cancel">
<field name="workflow" ref="wkf_forecast"/>
<field name="flow_stop">True</field>
<field name="name">Cancel</field>
<field name="kind">function</field>
<field name="action">set_state_cancel()</field>
</record>
<record model="workflow.activity" id="forecast_act_done">
<field name="workflow" ref="wkf_forecast"/>
<field name="flow_stop">True</field>
<field name="name">Done</field>
<field name="kind">function</field>
<field name="action">set_state_done()</field>
</record>
<record model="workflow.transition"
id="forecast_trans_draft_cancel">
<field name="act_from" ref="forecast_act_draft"/>
<field name="act_to" ref="forecast_act_cancel"/>
<field name="group" ref="stock.group_stock_admin"/>
<field name="signal">cancel</field>
</record>
<record model="workflow.transition"
id="forecast_trans_draft_done">
<field name="act_from" ref="forecast_act_draft"/>
<field name="act_to" ref="forecast_act_done"/>
<field name="group" ref="stock.group_stock_admin"/>
<field name="signal">done</field>
</record>
<record model="ir.model.access" id="access_forecast">
<field name="model" search="[('model', '=', 'stock.forecast')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_forecast_group_stock">
<field name="model" search="[('model', '=', 'stock.forecast')]"/>
<field name="group" ref="stock.group_stock"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_forecast_line">
<field name="model" search="[('model', '=', 'stock.forecast.line')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_forecast_line_group_stock">
<field name="model" search="[('model', '=', 'stock.forecast.line')]"/>
<field name="group" ref="stock.group_stock"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
</data>
</tryton>