Improved:

Sales Management
	Project Manegement
	Stock Management
	Accounting

bzr revid: fp@tinyerp.com-20080907232439-bod5fedw3o7w7u47
This commit is contained in:
Fabien Pinckaers 2008-09-08 01:24:39 +02:00
parent 426bc5164e
commit c949d120fc
28 changed files with 697 additions and 383 deletions

View File

@ -159,15 +159,6 @@ class account_account(osv.osv):
return super(account_account,self).search(cr, uid, args, offset, limit, return super(account_account,self).search(cr, uid, args, offset, limit,
order, context=context, count=count) order, context=context, count=count)
# def _credit(self, cr, uid, ids, field_name, arg, context={}):
# return self.__compute(cr, uid, ids, field_name, arg, context, 'COALESCE(SUM(l.credit), 0)')
#
# def _debit(self, cr, uid, ids, field_name, arg, context={}):
# return self.__compute(cr, uid, ids, field_name, arg, context, 'COALESCE(SUM(l.debit), 0)')
#
# def _balance(self, cr, uid, ids, field_name, arg, context={}):
# return self.__compute(cr, uid, ids, field_name, arg, context, 'COALESCE(SUM(l.debit) - SUM(l.credit), 0)')
def __compute(self, cr, uid, ids, field_names, arg, context={}, query=''): def __compute(self, cr, uid, ids, field_names, arg, context={}, query=''):
mapping = { mapping = {
'balance': "COALESCE(SUM(l.debit) - SUM(l.credit), 0) as balance ", 'balance': "COALESCE(SUM(l.debit) - SUM(l.credit), 0) as balance ",
@ -180,7 +171,7 @@ class account_account(osv.osv):
if ids2: if ids2:
query = self.pool.get('account.move.line')._query_get(cr, uid, query = self.pool.get('account.move.line')._query_get(cr, uid,
context=context) context=context)
cr.execute(("SELECT l.account_id, " +\ cr.execute(("SELECT l.account_id as id, " +\
' , '.join(map(lambda x: mapping[x], field_names)) + ' , '.join(map(lambda x: mapping[x], field_names)) +
"FROM " \ "FROM " \
"account_move_line l " \ "account_move_line l " \
@ -189,19 +180,16 @@ class account_account(osv.osv):
"AND " + query + " " \ "AND " + query + " " \
"GROUP BY l.account_id") % (acc_set, )) "GROUP BY l.account_id") % (acc_set, ))
for res in cr.fetchall(): for res in cr.dictfetchall():
accounts[res[0]] = res[1:] accounts[res['id']] = res
res = {} res = {}
for id in ids: for id in ids:
res[id] = map(lambda x: 0.0, field_names) res[id] = {}.fromkeys(field_names, 0.0)
ids2 = self.search(cr, uid, [('parent_id', 'child_of', [id])]) ids2 = self.search(cr, uid, [('parent_id', 'child_of', [id])])
for i in ids2: for i in ids2:
for a in range(len(field_names)): for a in field_names:
res[id][a] += accounts.get(i, (0.0,0.0,0.0))[a] res[id][a] += accounts.get(i, {}).get(a, 0.0)
# TODO: if account.type is consolidation: compute all childs like before +
# currency conversion
return res return res
def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}): def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}):
@ -233,6 +221,7 @@ class account_account(osv.osv):
('payable','Payable'), ('payable','Payable'),
('view','View'), ('view','View'),
('consolidation','Consolidation'), ('consolidation','Consolidation'),
('other','Others'),
('closed','Closed'), ('closed','Closed'),
], 'Internal Type', required=True,), ], 'Internal Type', required=True,),
@ -474,8 +463,8 @@ class account_fiscalyear(osv.osv):
while ds.strftime('%Y-%m-%d')<fy.date_stop: while ds.strftime('%Y-%m-%d')<fy.date_stop:
de = ds + RelativeDateTime(months=interval, days=-1) de = ds + RelativeDateTime(months=interval, days=-1)
self.pool.get('account.period').create(cr, uid, { self.pool.get('account.period').create(cr, uid, {
'name': ds.strftime('%d/%m') + ' - '+de.strftime('%d/%m'), 'name': ds.strftime('%m/%Y'),
'code': ds.strftime('%d/%m') + '-'+de.strftime('%d/%m'), 'code': ds.strftime('%m/%Y'),
'date_start': ds.strftime('%Y-%m-%d'), 'date_start': ds.strftime('%Y-%m-%d'),
'date_stop': de.strftime('%Y-%m-%d'), 'date_stop': de.strftime('%Y-%m-%d'),
'fiscalyear_id': fy.id, 'fiscalyear_id': fy.id,

View File

@ -5,10 +5,6 @@
<test expr="not len(line_id) or line_id[0].state != 'valid' or (sum([l.debit - l.credit for l in line_id]) &lt;= 0.00001)"/> <test expr="not len(line_id) or line_id[0].state != 'valid' or (sum([l.debit - l.credit for l in line_id]) &lt;= 0.00001)"/>
</assert> </assert>
<!--assert model="account.invoice" search="[]" string="If the invoice is paid, third party accounting lines must be reconciled">
<test expr="not state == 'paid' or ..." />
</assert-->
<assert model="account.account" search="[]" string="For all accounts, the balance is equal to the sum of the balance of its childs"> <assert model="account.account" search="[]" string="For all accounts, the balance is equal to the sum of the balance of its childs">
<test expr="not len(child_id) or (balance - sum([c.balance for c in child_id]) &lt;= 0.00001)"/> <test expr="not len(child_id) or (balance - sum([c.balance for c in child_id]) &lt;= 0.00001)"/>
</assert> </assert>
@ -29,4 +25,4 @@
<test expr="not parent_id or (code!='0')"/> <test expr="not parent_id or (code!='0')"/>
</assert> </assert>
</data> </data>
</terp> </terp>

View File

@ -319,7 +319,7 @@ class account_bank_statement(osv.osv):
cursor.execute('SELECT balance_end_real \ cursor.execute('SELECT balance_end_real \
FROM account_bank_statement \ FROM account_bank_statement \
WHERE journal_id = %d \ WHERE journal_id = %d \
ORDER BY date DESC LIMIT 1', (journal_id,)) ORDER BY date DESC,id DESC LIMIT 1', (journal_id,))
res = cursor.fetchone() res = cursor.fetchone()
balance_start = res and res[0] or 0.0 balance_start = res and res[0] or 0.0

View File

@ -1411,7 +1411,7 @@
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="res_model">account.config.fiscalyear</field> <field name="res_model">account.config.fiscalyear</field>
<field name="view_type">form</field> <field name="view_type">form</field>
<field name="view_mode">form</field> <field name="view_mode">tree</field>
<field name="target">new</field> <field name="target">new</field>
</record> </record>

View File

@ -49,7 +49,7 @@ your own chart of account.
<record id="cash" model="account.account"> <record id="cash" model="account.account">
<field name="name">Petty Cash</field> <field name="name">Petty Cash</field>
<field name="code">x 570000</field> <field name="code">x 570000</field>
<field name="type">cash</field> <field name="type">other</field>
<field name="user_type" ref="account.account_type_cash_moves"/> <field name="user_type" ref="account.account_type_cash_moves"/>
<field eval="ref('minimal_0')" name="parent_id"/> <field eval="ref('minimal_0')" name="parent_id"/>
<field name="company_id" ref="base.main_company"/> <field name="company_id" ref="base.main_company"/>
@ -64,7 +64,7 @@ your own chart of account.
<record id="a_expense" model="account.account"> <record id="a_expense" model="account.account">
<field name="name">Products Purchase</field> <field name="name">Products Purchase</field>
<field name="code">x 600000</field> <field name="code">x 600000</field>
<field name="type">expense</field> <field name="type">other</field>
<field name="user_type" ref="account.account_type_expense"/> <field name="user_type" ref="account.account_type_expense"/>
<field eval="ref('minimal_0')" name="parent_id"/> <field eval="ref('minimal_0')" name="parent_id"/>
<field name="company_id" ref="base.main_company"/> <field name="company_id" ref="base.main_company"/>
@ -78,7 +78,7 @@ your own chart of account.
<record id="a_sale" model="account.account"> <record id="a_sale" model="account.account">
<field name="name">Products Sales</field> <field name="name">Products Sales</field>
<field name="code">x 701000</field> <field name="code">x 701000</field>
<field name="type">income</field> <field name="type">other</field>
<field name="user_type" ref="account.account_type_income"/> <field name="user_type" ref="account.account_type_income"/>
<field eval="ref('minimal_0')" name="parent_id"/> <field eval="ref('minimal_0')" name="parent_id"/>
<field name="company_id" ref="base.main_company"/> <field name="company_id" ref="base.main_company"/>

View File

@ -55,13 +55,12 @@ class res_partner(osv.osv):
'credit': 'receivable', 'credit': 'receivable',
'debit': 'payable' 'debit': 'payable'
} }
maps = {} maps = {'receivable':'credit', 'payable':'debit' }
for i in range(len(field_names)): res = {}
maps[{'credit': 'receivable', 'debit': 'payable' }[field_names[i]]] = i for id in ids:
res = {}.fromkeys(ids, map(lambda x: 0.0, field_names)) res[id] = {}.fromkeys(field_names, 0)
for pid,type,val in cr.fetchall(): for pid,type,val in cr.fetchall():
if type in maps: res[pid][maps[type]] = val
res[pid][maps[type]] = val
return res return res
def _credit_search(self, cr, uid, obj, name, args): def _credit_search(self, cr, uid, obj, name, args):

View File

@ -667,11 +667,16 @@ class hr_timesheet_sheet_sheet_account(osv.osv):
hr_timesheet_sheet_sheet_account() hr_timesheet_sheet_sheet_account()
class res_company(osv.osv): class res_company(osv.osv):
_inherit = 'res.company' _inherit = 'res.company'
_columns = { _columns = {
'timesheet_range': fields.selection([('day','Day'),('week','Week'),('month','Month'),('year','Year')], 'Timeshet range'), 'timesheet_range': fields.selection(
'timesheet_max_difference': fields.float('Timesheet allowed difference', help="Allowed difference between the sign in/out and the timesheet computation for one sheet. Set this to 0 if you do not want any control."), [('day','Day'),('week','Week'),('month','Month'),('year','Year')], 'Timeshet range',
required=True),
'timesheet_max_difference': fields.float('Timesheet allowed difference',
help="Allowed difference between the sign in/out and the timesheet " \
"computation for one sheet. Set this to 0 if you do not want any control."),
} }
_defaults = { _defaults = {
'timesheet_range': lambda *args: 'month', 'timesheet_range': lambda *args: 'month',

View File

@ -150,16 +150,16 @@
<field name="total_difference" widget="float_time"/> <field name="total_difference" widget="float_time"/>
</page> </page>
<!-- <!--
<page string="By account"> <page string="By account">
<field name="account_ids" colspan="4" nolabel="1"> <field name="account_ids" colspan="4" nolabel="1">
<tree string="Analytic accounts"> <tree string="Analytic accounts">
<field name="name"/> <field name="name"/>
<field name="total" widget="float_time"/> <field name="total" widget="float_time"/>
<field name="invoice_rate"/> <field name="invoice_rate"/>
</tree> </tree>
</field> </field>
</page> </page>
--> -->
</notebook> </notebook>
<field name="state"/> <field name="state"/>
<group col="4" colspan="2"> <group col="4" colspan="2">
@ -238,8 +238,8 @@
<menuitem action="act_hr_timesheet_sheet_form_all_valid" id="menu_act_hr_timesheet_sheet_form_all_valid" parent="hr_timesheet_sheet.menu_act_hr_timesheet_sheet_form"/> <menuitem action="act_hr_timesheet_sheet_form_all_valid" id="menu_act_hr_timesheet_sheet_form_all_valid" parent="hr_timesheet_sheet.menu_act_hr_timesheet_sheet_form"/>
<!-- <!--
Company inheritancy Company inheritancy
--> -->
<record id="hr_timesheet_sheet_company" model="ir.ui.view"> <record id="hr_timesheet_sheet_company" model="ir.ui.view">
<field name="name">res.company.sheet</field> <field name="name">res.company.sheet</field>
@ -247,18 +247,18 @@
<field name="type">form</field> <field name="type">form</field>
<field name="inherit_id" ref="base.view_company_form"/> <field name="inherit_id" ref="base.view_company_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="rml_footer2" position="after"> <page string="Configuration" position="inside">
<newline/> <separator string="Timesheets" colspan="4"/>
<field name="timesheet_range"/> <field name="timesheet_range"/>
<field name="timesheet_max_difference"/> <field name="timesheet_max_difference"/>
<newline/> <newline/>
</field> </page>
</field> </field>
</record> </record>
<!-- <!--
hr.analytic.timesheet inheritancy hr.analytic.timesheet inheritancy
--> -->
<record id="hr_timesheet_line_form" model="ir.ui.view"> <record id="hr_timesheet_line_form" model="ir.ui.view">
<field name="name">hr.analytic.timesheet.form</field> <field name="name">hr.analytic.timesheet.form</field>
@ -271,10 +271,9 @@
</field> </field>
</field> </field>
</record> </record>
<!-- <!--
hr.attendance inheritancy hr.attendance inheritancy
--> -->
<record id="view_attendance_form" model="ir.ui.view"> <record id="view_attendance_form" model="ir.ui.view">
<field name="name">hr.attendance.form</field> <field name="name">hr.attendance.form</field>

View File

@ -1124,11 +1124,11 @@ class StockPicking(osv.osv):
# #
# Explode picking by replacing phantom BoMs # Explode picking by replacing phantom BoMs
# #
def action_explode(self, cr, uid, ids, *args): def action_explode(self, cr, uid, picks, *args):
for pick in self.browse(cr, uid, ids): for pick in picks:
for move in pick.move_lines: for move in pick.move_lines:
self.pool.get('stock.move')._action_explode(cr, uid, move) self.pool.get('stock.move')._action_explode(cr, uid, move)
return True return picks
StockPicking() StockPicking()

View File

@ -260,8 +260,8 @@ class product_template(osv.osv):
'warranty': fields.float('Warranty (months)'), 'warranty': fields.float('Warranty (months)'),
'sale_ok': fields.boolean('Can be sold', help="Determine if the product can be visible in the list of product within a selection from a sale order line."), 'sale_ok': fields.boolean('Can be sold', help="Determine if the product can be visible in the list of product within a selection from a sale order line."),
'purchase_ok': fields.boolean('Can be Purchased', help="Determine if the product is visible in the list of products within a selection from a purchase order line."), 'purchase_ok': fields.boolean('Can be Purchased', help="Determine if the product is visible in the list of products within a selection from a purchase order line."),
'uom_id': fields.many2one('product.uom', 'Default UOM', required=True), 'uom_id': fields.many2one('product.uom', 'Default UoM', required=True, help="This is the default Unit of Measure used for all stock operation."),
'uom_po_id': fields.many2one('product.uom', 'Purchase UOM', required=True), 'uom_po_id': fields.many2one('product.uom', 'Purchase UoM', required=True),
'state': fields.selection([('',''),('draft', 'In Development'),('sellable','In Production'),('end','End of Lifecycle'),('obsolete','Obsolete')], 'Status', help="Tells the user if he can use the product or not."), 'state': fields.selection([('',''),('draft', 'In Development'),('sellable','In Production'),('end','End of Lifecycle'),('obsolete','Obsolete')], 'Status', help="Tells the user if he can use the product or not."),
'uos_id' : fields.many2one('product.uom', 'Unit of Sale', 'uos_id' : fields.many2one('product.uom', 'Unit of Sale',
help='Keep empty to use the default UOM'), help='Keep empty to use the default UOM'),
@ -347,10 +347,7 @@ class product_product(osv.osv):
def _get_product_available_func(states, what): def _get_product_available_func(states, what):
def _product_available(self, cr, uid, ids, name, arg, context={}): def _product_available(self, cr, uid, ids, name, arg, context={}):
res={} return {}.fromkeys(ids, 0.0)
for id in ids:
res.setdefault(id, 0.0)
return res
return _product_available return _product_available
_product_qty_available = _get_product_available_func(('done',), ('in', 'out')) _product_qty_available = _get_product_available_func(('done',), ('in', 'out'))

View File

@ -170,7 +170,7 @@
<field name="name">product.category.list</field> <field name="name">product.category.list</field>
<field name="model">product.category</field> <field name="model">product.category</field>
<field name="type">tree</field> <field name="type">tree</field>
<field name="sequence">1</field> <field name="priority">1</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Product Categories"> <tree string="Product Categories">
<field name="complete_name"/> <field name="complete_name"/>

View File

@ -26,7 +26,9 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
############################################################################### ###############################################################################
import project import project
import company
import report import report
import wizard import wizard

50
addons/project/company.py Normal file
View File

@ -0,0 +1,50 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
#
# $Id$
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from osv import fields
from osv import osv
class res_company(osv.osv):
_inherit = 'res.company'
_columns = {
'project_time_mode': fields.selection(
[('hours','Hours'),('day','Days'),('week','Weeks'),('month','Months')],
'Project Time Unit',
help='This will set the unit of measure used in projects and tasks.\n' \
"If you use the timesheet linked to projects (project_timesheet module), don't " \
"forget to setup the right unit of measure in your employees.",
required=True,
),
}
_defaults = {
'project_time_mode': lambda *args: 'hours',
}
res_company()

View File

@ -28,13 +28,12 @@
# #
############################################################################## ##############################################################################
from lxml import etree
from mx import DateTime from mx import DateTime
from mx.DateTime import now from mx.DateTime import now
import time import time
import netsvc
from osv import fields, osv from osv import fields, osv
import ir
class project(osv.osv): class project(osv.osv):
_name = "project.project" _name = "project.project"
@ -65,7 +64,7 @@ class project(osv.osv):
ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)]) ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)])
res_sum = {} res_sum = {}
if ids2: if ids2:
cr.execute('SELECT project_id, COALESCE(SUM(planned_hours),0) \ cr.execute('SELECT project_id, COALESCE(SUM(total_hours),0) \
FROM project_task \ FROM project_task \
WHERE project_id IN (' + ','.join([str(x) for x in ids2]) + ') \ WHERE project_id IN (' + ','.join([str(x) for x in ids2]) + ') \
AND active \ AND active \
@ -97,7 +96,7 @@ class project(osv.osv):
if not ids: if not ids:
return res return res
cr.execute('''SELECT cr.execute('''SELECT
project_id, sum(progress*planned_hours), sum(planned_hours) project_id, sum(progress*total_hours), sum(total_hours)
FROM FROM
project_task project_task
WHERE WHERE
@ -132,7 +131,7 @@ class project(osv.osv):
'warn_header': fields.text('Mail header'), 'warn_header': fields.text('Mail header'),
'warn_footer': fields.text('Mail footer'), 'warn_footer': fields.text('Mail footer'),
'notes': fields.text('Notes'), 'notes': fields.text('Notes'),
'timesheet_id': fields.many2one('hr.timesheet.group', 'Working hours'), 'timesheet_id': fields.many2one('hr.timesheet.group', 'Working Time'),
'state': fields.selection([('open', 'Open'),('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'Status', required=True), 'state': fields.selection([('open', 'Open'),('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'Status', required=True),
} }
@ -203,16 +202,30 @@ class task(osv.osv):
t3 += map(lambda x: (x,t2[1]+1), t2[0].child_ids) t3 += map(lambda x: (x,t2[1]+1), t2[0].child_ids)
return result return result
def _hours_effect(self, cr, uid, ids, name, args, context): # Compute: effective_hours, total_hours, progress
def _hours_get(self, cr, uid, ids, field_names, args, context):
task_set = ','.join(map(str, ids)) task_set = ','.join(map(str, ids))
cr.execute(("SELECT task_id, COALESCE(SUM(hours),0) FROM project_task_work WHERE task_id in (%s) GROUP BY task_id") % (task_set,)) cr.execute(("SELECT task_id, COALESCE(SUM(hours),0) FROM project_task_work WHERE task_id in (%s) GROUP BY task_id") % (task_set,))
hours = dict(cr.fetchall())
res = {} res = {}
for id in ids: for task in self.browse(cr, uid, ids, context=context):
res[id] = 0.0 res[task.id] = {}
for task_id, sum in cr.fetchall(): res[task.id]['effective_hours'] = hours.get(task.id, 0.0)
res[task_id] = sum res[task.id]['total_hours'] = task.remaining_hours + hours.get(task.id, 0.0)
if (task.remaining_hours + hours.get(task.id, 0.0)):
res[task.id]['progress'] = min(100.0 * hours.get(task.id, 0.0) / res[task.id]['total_hours'], 100)
else:
res[task.id]['progress'] = 0.0
res[task.id]['delay_hours'] = res[task.id]['total_hours'] - task.planned_hours
return res return res
def onchange_planned(self, cr, uid, ids, planned, effective):
return {'value':{'remaining_hours': planned-effective}}
#_sql_constraints = [
# ('remaining_hours', 'CHECK (remaining_hours>=0)', 'Please increase and review remaining hours ! It can not be smaller than 0.'),
#]
_columns = { _columns = {
'active': fields.boolean('Active'), 'active': fields.boolean('Active'),
'name': fields.char('Task summary', size=128, required=True), 'name': fields.char('Task summary', size=128, required=True),
@ -220,7 +233,7 @@ class task(osv.osv):
'priority' : fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Urgent'), ('0','Very urgent')], 'Importance'), 'priority' : fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Urgent'), ('0','Very urgent')], 'Importance'),
'sequence': fields.integer('Sequence'), 'sequence': fields.integer('Sequence'),
'type': fields.many2one('project.task.type', 'Type'), 'type': fields.many2one('project.task.type', 'Type'),
'state': fields.selection([('draft', 'Draft'),('open', 'Open'),('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'Status'), 'state': fields.selection([('draft', 'Draft'),('open', 'Open'),('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'Status', readonly=True, required=True),
'date_start': fields.datetime('Date'), 'date_start': fields.datetime('Date'),
'date_deadline': fields.datetime('Deadline'), 'date_deadline': fields.datetime('Deadline'),
'date_close': fields.datetime('Date Closed', readonly=True), 'date_close': fields.datetime('Date Closed', readonly=True),
@ -230,16 +243,21 @@ class task(osv.osv):
'history': fields.function(_history_get, method=True, string="Task Details", type="text"), 'history': fields.function(_history_get, method=True, string="Task Details", type="text"),
'notes': fields.text('Notes'), 'notes': fields.text('Notes'),
'start_sequence': fields.boolean('Wait for previous sequences'), 'start_sequence': fields.boolean('Wait for previous sequences'),
'planned_hours': fields.float('Plan. hours'),
'effective_hours': fields.function(_hours_effect, method=True, string='Eff. Hours'), 'planned_hours': fields.float('Planned Hours', readonly=True, states={'draft':[('readonly',False)]}),
'progress': fields.integer('Progress (0-100)'), 'effective_hours': fields.function(_hours_get, method=True, string='Hours Spent', multi='hours', store=True),
'remaining_hours': fields.float('Remaining Hours', digits=(16,2)),
'total_hours': fields.function(_hours_get, method=True, string='Total Hours', multi='hours', store=True),
'progress': fields.function(_hours_get, method=True, string='Progress (%)', multi='hours', store=True),
'delay_hours': fields.function(_hours_get, method=True, string='Delay Hours', multi='hours', store=True),
'user_id': fields.many2one('res.users', 'Assigned to'), 'user_id': fields.many2one('res.users', 'Assigned to'),
'partner_id': fields.many2one('res.partner', 'Partner'), 'partner_id': fields.many2one('res.partner', 'Partner'),
'work_ids': fields.one2many('project.task.work', 'task_id', 'Work done'), 'work_ids': fields.one2many('project.task.work', 'task_id', 'Work done', readonly=False, states={'draft':[('readonly',True)]}),
} }
_defaults = { _defaults = {
'user_id': lambda obj,cr,uid,context: uid, 'user_id': lambda obj,cr,uid,context: uid,
'state': lambda *a: 'open', 'state': lambda *a: 'draft',
'priority': lambda *a: '2', 'priority': lambda *a: '2',
'progress': lambda *a: 0, 'progress': lambda *a: 0,
'sequence': lambda *a: 10, 'sequence': lambda *a: 10,
@ -249,6 +267,31 @@ class task(osv.osv):
} }
_order = "state, sequence, priority, date_deadline, id" _order = "state, sequence, priority, date_deadline, id"
#
# Override view according to the company definition
#
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False):
tm = self.pool.get('res.users').browse(cr, uid, uid, context).company_id.project_time_mode
f = self.pool.get('res.company').fields_get(cr, uid, ['project_time_mode'], context)
word = dict(f['project_time_mode']['selection'])[tm]
res = super(task, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar)
if tm=='hours':
return res
eview = etree.fromstring(res['arch'])
def _check_rec(eview, tm):
if eview.attrib.get('widget',False) == 'float_time':
eview.set('widget','float')
for child in eview:
_check_rec(child, tm)
return True
_check_rec(eview, tm)
res['arch'] = etree.tostring(eview)
for f in res['fields']:
if 'Hours' in res['fields'][f]['string']:
res['fields'][f]['string'] = res['fields'][f]['string'].replace('Hours',word)
return res
def do_close(self, cr, uid, ids, *args): def do_close(self, cr, uid, ids, *args):
request = self.pool.get('res.request') request = self.pool.get('res.request')
tasks = self.browse(cr, uid, ids) tasks = self.browse(cr, uid, ids)
@ -265,7 +308,7 @@ class task(osv.osv):
'ref_doc1': 'project.task,%d'% (task.id,), 'ref_doc1': 'project.task,%d'% (task.id,),
'ref_doc2': 'project.project,%d'% (project.id,), 'ref_doc2': 'project.project,%d'% (project.id,),
}) })
self.write(cr, uid, [task.id], {'state': 'done', 'date_close':time.strftime('%Y-%m-%d %H:%M:%S'), 'progress': 100}) self.write(cr, uid, [task.id], {'state': 'done', 'date_close':time.strftime('%Y-%m-%d %H:%M:%S'), 'remaining_hours': 0.0})
if task.parent_id and task.parent_id.state in ('pending','draft'): if task.parent_id and task.parent_id.state in ('pending','draft'):
self.do_reopen(cr, uid, [task.parent_id.id]) self.do_reopen(cr, uid, [task.parent_id.id])
return True return True
@ -304,7 +347,7 @@ class task(osv.osv):
'ref_doc1': 'project.task,%d' % task.id, 'ref_doc1': 'project.task,%d' % task.id,
'ref_doc2': 'project.project,%d' % project.id, 'ref_doc2': 'project.project,%d' % project.id,
}) })
self.write(cr, uid, [task.id], {'state': 'cancelled', 'progress':100}) self.write(cr, uid, [task.id], {'state': 'cancelled', 'remaining_hours':0.0})
return True return True
def do_open(self, cr, uid, ids, *args): def do_open(self, cr, uid, ids, *args):
@ -331,7 +374,7 @@ class project_work(osv.osv):
_columns = { _columns = {
'name': fields.char('Work summary', size=128), 'name': fields.char('Work summary', size=128),
'date': fields.datetime('Date'), 'date': fields.datetime('Date'),
'task_id': fields.many2one('project.task', 'Task', ondelete='cascade'), 'task_id': fields.many2one('project.task', 'Task', ondelete='cascade', required=True),
'hours': fields.float('Hours spent'), 'hours': fields.float('Hours spent'),
'user_id': fields.many2one('res.users', 'Done by', required=True), 'user_id': fields.many2one('res.users', 'Done by', required=True),
} }
@ -340,6 +383,20 @@ class project_work(osv.osv):
'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S') 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S')
} }
_order = "date desc" _order = "date desc"
def create(self, cr, uid, vals, *args, **kwargs):
if 'task_id' in vals:
cr.execute('update project_task set remaining_hours=remaining_hours+%.2f where id=%d', (-vals.get('hours',0.0), vals['task_id']))
return super(project_work,self).create(cr, uid, vals, *args, **kwargs)
def write(self, cr, uid, ids,vals,context={}):
for work in self.browse(cr, uid, ids, context):
cr.execute('update project_task set remaining_hours=remaining_hours+%.2f+(%.2f) where id=%d', (-vals.get('hours',0.0), work.hours, work.task_id.id))
return super(project_work,self).write(cr, uid, ids, vals, context)
def unlink(self, cr, uid, ids, *args, **kwargs):
for work in self.browse(cr, uid, ids):
cr.execute('update project_task set remaining_hours=remaining_hours+%.2f where id=%d', (work.hours, work.task_id.id))
return super(project_work,self).unlink(cr, uid, ids,*args, **kwargs)
project_work() project_work()

View File

@ -26,12 +26,12 @@
<field name="active" select="2"/> <field name="active" select="2"/>
<button name="toggleActive" string="Toggle activity" type="object"/> <button name="toggleActive" string="Toggle activity" type="object"/>
</group> </group>
<field name="warn_manager"/>
<newline/>
<field name="planned_hours" widget="float_time"/> <field name="planned_hours" widget="float_time"/>
<field name="effective_hours" widget="float_time"/> <field name="effective_hours" widget="float_time"/>
<field name="warn_manager"/>
<field name="timesheet_id"/> <field name="timesheet_id"/>
<field name="state"/> <field name="state"/>
<newline/>
<separator colspan="4" string="Project's members"/> <separator colspan="4" string="Project's members"/>
<field colspan="4" name="members" nolabel="1"/> <field colspan="4" name="members" nolabel="1"/>
</page> </page>
@ -209,18 +209,25 @@
<field eval="2" name="priority"/> <field eval="2" name="priority"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Task edition"> <form string="Task edition">
<field name="name" select="1"/> <group colspan="6" col="6">
<field name="project_id" required="1" select="1"/> <field name="name" select="1"/>
<field name="user_id" select="1"/> <field name="project_id" required="1" select="1"/>
<field name="planned_hours" widget="float_time"/> <field name="total_hours" widget="float_time"/>
<field name="user_id" select="1"/>
<field name="date_deadline" select="2"/>
<field name="progress" widget="progressbar"/>
</group>
<notebook colspan="4"> <notebook colspan="4">
<page string="Information"> <page string="Information">
<field name="date_deadline" select="2"/> <field
<field name="priority"/> name="planned_hours"
<separator colspan="4" string="Description"/> widget="float_time"
<field colspan="4" name="description" nolabel="1" select="2"/> on_change="onchange_planned(planned_hours,effective_hours)"/>
<field name="delay_hours" widget="float_time"/>
<field name="remaining_hours" select="2" widget="float_time"/>
<field name="effective_hours" widget="float_time"/> <field name="effective_hours" widget="float_time"/>
<field name="progress"/>
<field colspan="4" name="description" nolabel="1" select="2"/>
<field colspan="4" name="work_ids" nolabel="1"/> <field colspan="4" name="work_ids" nolabel="1"/>
<newline/> <newline/>
<group col="11" colspan="4"> <group col="11" colspan="4">
@ -250,10 +257,14 @@
<field colspan="4" name="parent_id"/> <field colspan="4" name="parent_id"/>
</page> </page>
<page groups="base.group_extended" string="Extra Info"> <page groups="base.group_extended" string="Extra Info">
<separator string="Planning" colspan="2"/>
<separator string="Dates" colspan="2"/>
<field name="priority"/>
<field name="date_start" select="2"/> <field name="date_start" select="2"/>
<field name="date_close" select="2"/>
<field name="type"/>
<field name="sequence"/> <field name="sequence"/>
<field name="date_close" select="2"/>
<field name="type"/>
<field name="active" select="2"/> <field name="active" select="2"/>
<field name="start_sequence"/> <field name="start_sequence"/>
<field name="partner_id" select="2"/> <field name="partner_id" select="2"/>
@ -270,17 +281,18 @@
<field name="type">tree</field> <field name="type">tree</field>
<field eval="2" name="priority"/> <field eval="2" name="priority"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree colors="grey:state in ('cancel','done');blue:state=='pending'" string="Tasks"> <tree colors="grey:state in ('cancel','done');blue:remaining_hours&lt;0;red:bool(date_deadline) &amp; (date_deadline&lt;current_date) &amp; (state in ('draft','open'))" string="Tasks">
<field name="sequence"/> <field name="sequence"/>
<field name="name"/> <field name="name"/>
<field name="project_id"/> <field name="project_id"/>
<field name="user_id"/> <field name="user_id"/>
<field name="date_deadline"/> <field name="date_deadline"/>
<field name="planned_hours" sum="Planned hours" widget="float_time"/> <field name="planned_hours" sum="Planned" widget="float_time"/>
<field name="effective_hours" sum="Effective hours" widget="float_time"/> <field name="delay_hours" sum="Delay" widget="float_time"/>
<field name="progress" widget="progressbar"/> <field name="progress" widget="progressbar"/>
<field name="priority"/> <field name="priority"/>
<field name="state"/> <field name="state"/>
<field name="remaining_hours" invisible="1"/>
</tree> </tree>
</field> </field>
</record> </record>
@ -444,6 +456,20 @@
view_mode="tree,form,calendar" view_mode="tree,form,calendar"
view_type="form"/> view_type="form"/>
<record id="task_company" model="ir.ui.view">
<field name="name">res.company.task.config</field>
<field name="model">res.company</field>
<field name="type">form</field>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="arch" type="xml">
<page string="Configuration" position="inside">
<separator string="Project Management" colspan="4"/>
<field name="project_time_mode"/>
<newline/>
</page>
</field>
</record>
<act_window domain="[('user_id', '=', active_id),('state', '&lt;&gt;', 'cancelled'),('state', '&lt;&gt;', 'done')]" id="act_res_users_2_project_task_opened" name="Assigned tasks" res_model="project.task" src_model="res.users" view_mode="tree,form" view_type="form"/> <act_window domain="[('user_id', '=', active_id),('state', '&lt;&gt;', 'cancelled'),('state', '&lt;&gt;', 'done')]" id="act_res_users_2_project_task_opened" name="Assigned tasks" res_model="project.task" src_model="res.users" view_mode="tree,form" view_type="form"/>
<act_window domain="[('user_id', '=', active_id),('date', '&gt;=', time.strftime('%Y-%m-01'))]" id="act_res_users_2_project_task_work_month" name="Month works" res_model="project.task.work" src_model="res.users" view_mode="tree,form" view_type="form"/> <act_window domain="[('user_id', '=', active_id),('date', '&gt;=', time.strftime('%Y-%m-01'))]" id="act_res_users_2_project_task_work_month" name="Month works" res_model="project.task.work" src_model="res.users" view_mode="tree,form" view_type="form"/>

View File

@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<terp> <terp>
<data> <data>
<wizard id="wizard_billing" model="project.task" name="project.wiz_bill" string="Bill tasks"/>
<wizard id="wizard_close_task" menu="False" model="project.task" name="project.task.close" string="Close Task"/> <wizard id="wizard_close_task" menu="False" model="project.task" name="project.task.close" string="Close Task"/>
<wizard id="wizard_delegate_task" menu="False" model="project.task" name="project.task.delegate" string="Delegate Task"/> <wizard id="wizard_delegate_task" menu="False" model="project.task" name="project.task.delegate" string="Delegate Task"/>
</data> </data>
</terp> </terp>

View File

@ -620,13 +620,6 @@ class sale_order_line(osv.osv):
res[line.id] = 1 res[line.id] = 1
return res return res
def _get_1st_packaging(self, cr, uid, context={}):
cr.execute('select id from product_packaging order by id asc limit 1')
res = cr.fetchone()
if not res:
return False
return res[0]
_name = 'sale.order.line' _name = 'sale.order.line'
_description = 'Sale Order line' _description = 'Sale Order line'
_columns = { _columns = {
@ -667,7 +660,7 @@ class sale_order_line(osv.osv):
'invoiced': lambda *a: 0, 'invoiced': lambda *a: 0,
'state': lambda *a: 'draft', 'state': lambda *a: 'draft',
'type': lambda *a: 'make_to_stock', 'type': lambda *a: 'make_to_stock',
'product_packaging': _get_1st_packaging, 'product_packaging': lambda *a: False
} }
def invoice_line_create(self, cr, uid, ids, context={}): def invoice_line_create(self, cr, uid, ids, context={}):
def _get_line_qty(line): def _get_line_qty(line):
@ -766,7 +759,7 @@ class sale_order_line(osv.osv):
def product_id_change(self, cr, uid, ids, pricelist, product, qty=0, def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
uom=False, qty_uos=0, uos=False, name='', partner_id=False, uom=False, qty_uos=0, uos=False, name='', partner_id=False,
lang=False, update_tax=True, date_order=False): lang=False, update_tax=True, date_order=False, packaging=False):
product_uom_obj = self.pool.get('product.uom') product_uom_obj = self.pool.get('product.uom')
partner_obj = self.pool.get('res.partner') partner_obj = self.pool.get('res.partner')
product_obj = self.pool.get('product.product') product_obj = self.pool.get('product.product')
@ -776,7 +769,7 @@ class sale_order_line(osv.osv):
context = {'lang': lang, 'partner_id': partner_id} context = {'lang': lang, 'partner_id': partner_id}
if not product: if not product:
return {'value': {'th_weight' : 0, return {'value': {'th_weight' : 0, 'product_packaging': False,
'product_uos_qty': qty}, 'domain': {'product_uom': [], 'product_uos_qty': qty}, 'domain': {'product_uom': [],
'product_uos': []}} 'product_uos': []}}
@ -787,6 +780,16 @@ class sale_order_line(osv.osv):
if not date_order: if not date_order:
date_order = time.strftime('%Y-%m-%d') date_order = time.strftime('%Y-%m-%d')
result = {}
product_obj = product_obj.browse(cr, uid, product, context=context)
if packaging:
default_uom = product_obj.uom_id and product_obj.uom_id.id
pack = self.pool.get('product.packaging').browse(cr, uid, packaging, context)
q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
qty = qty - qty % q + q
result['product_uom_qty'] = qty
price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist], price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
product, qty or 1.0, partner_id, { product, qty or 1.0, partner_id, {
'uom': uom, 'uom': uom,
@ -797,35 +800,34 @@ class sale_order_line(osv.osv):
_("Couldn't find a pricelist line matching this product and quantity.\n" _("Couldn't find a pricelist line matching this product and quantity.\n"
"You have to change either the product, the quantity or the pricelist.")) "You have to change either the product, the quantity or the pricelist."))
product = product_obj.browse(cr, uid, product, context=context)
if uom: if uom:
uom2 = product_uom_obj.browse(cr, uid, uom) uom2 = product_uom_obj.browse(cr, uid, uom)
if product.uom_id.category_id.id <> uom2.category_id.id: if product_obj.uom_id.category_id.id <> uom2.category_id.id:
uom = False uom = False
if uos: if uos:
if product.uos_id: if product_obj.uos_id:
uos2 = product_uom_obj.browse(cr, uid, uos) uos2 = product_uom_obj.browse(cr, uid, uos)
if product.uos_id.category_id.id <> uos2.category_id.id: if product_obj.uos_id.category_id.id <> uos2.category_id.id:
uos = False uos = False
else: else:
uos = False uos = False
result = {'price_unit': price, 'type': product.procure_method} result .update({'price_unit': price, 'type': product_obj.procure_method})
if product.description_sale: if product_obj.description_sale:
result['notes'] = product.description_sale result['notes'] = product_obj.description_sale
if update_tax: #The quantity only have changed if update_tax: #The quantity only have changed
result['delay'] = (product.sale_delay or 0.0) result['delay'] = (product_obj.sale_delay or 0.0)
taxes = self.pool.get('account.tax').browse(cr, uid, taxes = self.pool.get('account.tax').browse(cr, uid,
[x.id for x in product.taxes_id]) [x.id for x in product_obj.taxes_id])
taxep = None taxep = None
if partner_id: if partner_id:
taxep = self.pool.get('res.partner').browse(cr, uid, taxep = self.pool.get('res.partner').browse(cr, uid,
partner_id).property_account_tax partner_id).property_account_tax
if not taxep or not taxep.id: if not taxep or not taxep.id:
result['tax_id'] = [x.id for x in product.taxes_id] result['tax_id'] = [x.id for x in product_obj.taxes_id]
else: else:
res5 = [taxep.id] res5 = [taxep.id]
for t in taxes: for t in taxes:
@ -833,38 +835,39 @@ class sale_order_line(osv.osv):
res5.append(t.id) res5.append(t.id)
result['tax_id'] = res5 result['tax_id'] = res5
result['name'] = product.partner_ref result['name'] = product_obj.partner_ref
domain = {} domain = {}
if not uom and not uos: if not uom and not uos:
result['product_uom'] = product.uom_id.id result['product_uom'] = product_obj.uom_id.id
if product.uos_id: if product_obj.uos_id:
result['product_uos'] = product.uos_id.id result['product_uos'] = product_obj.uos_id.id
result['product_uos_qty'] = qty * product.uos_coeff result['product_uos_qty'] = qty * product_obj.uos_coeff
uos_category_id = product.uos_id.category_id.id uos_category_id = product_obj.uos_id.category_id.id
else: else:
result['product_uos'] = False result['product_uos'] = False
result['product_uos_qty'] = qty result['product_uos_qty'] = qty
uos_category_id = False uos_category_id = False
result['th_weight'] = qty * product.weight result['th_weight'] = qty * product_obj.weight
domain = {'product_uom': domain = {'product_uom':
[('category_id', '=', product.uom_id.category_id.id)], [('category_id', '=', product_obj.uom_id.category_id.id)],
'product_uos': 'product_uos':
[('category_id', '=', uos_category_id)]} [('category_id', '=', uos_category_id)]}
elif uom: # whether uos is set or not elif uom: # whether uos is set or not
default_uom = product.uom_id and product.uom_id.id default_uom = product_obj.uom_id and product_obj.uom_id.id
q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom) q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
if product.uos_id: if product_obj.uos_id:
result['product_uos'] = product.uos_id.id result['product_uos'] = product_obj.uos_id.id
result['product_uos_qty'] = q * product.uos_coeff result['product_uos_qty'] = q * product_obj.uos_coeff
else: else:
result['product_uos'] = False result['product_uos'] = False
result['product_uos_qty'] = q result['product_uos_qty'] = q
result['th_weight'] = q * product.weight result['th_weight'] = q * product_obj.weight
elif uos: # only happens if uom is False elif uos: # only happens if uom is False
result['product_uom'] = product.uom_id and product.uom_id.id result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id
result['product_uom_qty'] = qty_uos / product.uos_coeff result['product_uom_qty'] = qty_uos / product_obj.uos_coeff
result['th_weight'] = result['product_uom_qty'] * product.weight result['th_weight'] = result['product_uom_qty'] * product_obj.weight
# Round the quantity up
return {'value': result, 'domain': domain} return {'value': result, 'domain': domain}
def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0, def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0,

View File

@ -91,9 +91,24 @@
<notebook> <notebook>
<page string="Order Line"> <page string="Order Line">
<separator colspan="4" string="Automatic Declaration"/> <separator colspan="4" string="Automatic Declaration"/>
<field colspan="4" context="partner_id=parent.partner_id,quantity=product_uom_qty,pricelist=parent.pricelist_id,shop=parent.shop_id,uom=product_uom" name="product_id" on_change="product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id, 'lang' in context and context['lang'], True, parent.date_order)" select="1"/> <field colspan="4"
<field context="partner_id=parent.partner_id,quantity=product_uom_qty,pricelist=parent.pricelist_id,shop=parent.shop_id,uom=product_uom" name="product_uom_qty" on_change="product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id, 'lang' in context and context['lang'], False, parent.date_order)" select="1"/> context="partner_id=parent.partner_id,quantity=product_uom_qty,pricelist=parent.pricelist_id,shop=parent.shop_id,uom=product_uom"
<field name="product_uom" on_change="product_uom_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id, 'lang' in context and context['lang'], False, parent.date_order)"/> name="product_id"
on_change="product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id, 'lang' in context and context['lang'], True, parent.date_order, product_packaging)"
select="1"/>
<field
context="partner_id=parent.partner_id,quantity=product_uom_qty,pricelist=parent.pricelist_id,shop=parent.shop_id,uom=product_uom"
name="product_uom_qty"
on_change="product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id, 'lang' in context and context['lang'], False, parent.date_order, product_packaging)"
select="1"/>
<field name="product_uom"
on_change="product_uom_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id, 'lang' in context and context['lang'], False, parent.date_order)"/>
<field
name="product_packaging"
context="partner_id=parent.partner_id,quantity=product_uom_qty,pricelist=parent.pricelist_id,shop=parent.shop_id,uom=product_uom"
on_change="product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id, 'lang' in context and context['lang'], False, parent.date_order, product_packaging)"
domain="[('product_id','=',product_id)]"
groups="base.group_extended"/>
<separator colspan="4" string="Manual Description"/> <separator colspan="4" string="Manual Description"/>
<field colspan="4" name="name" select="2"/> <field colspan="4" name="name" select="2"/>
<field name="price_unit" select="2"/> <field name="price_unit" select="2"/>
@ -112,7 +127,6 @@
<page groups="base.group_extended" string="Extra Info"> <page groups="base.group_extended" string="Extra Info">
<field groups="product.group_uos" name="product_uos_qty" on_change="uos_change(product_uos, product_uos_qty, product_id)"/> <field groups="product.group_uos" name="product_uos_qty" on_change="uos_change(product_uos, product_uos_qty, product_id)"/>
<field groups="product.group_uos" name="product_uos"/> <field groups="product.group_uos" name="product_uos"/>
<field name="product_packaging"/>
<field name="address_allotment_id" select="2"/> <field name="address_allotment_id" select="2"/>
<separator colspan="4" string="Properties"/> <separator colspan="4" string="Properties"/>
<field colspan="4" name="property_ids" nolabel="1"/> <field colspan="4" name="property_ids" nolabel="1"/>
@ -209,7 +223,7 @@
<menuitem action="action_order_tree_all" id="menu_action_order_tree_all" parent="sale.menu_sale_order"/> <menuitem action="action_order_tree_all" id="menu_action_order_tree_all" parent="sale.menu_sale_order"/>
<record id="action_order_tree_new" model="ir.actions.act_window"> <record id="action_order_tree_new" model="ir.actions.act_window">
<field name="name">New Sale Order / Quotation</field> <field name="name">New Quotation</field>
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="res_model">sale.order</field> <field name="res_model">sale.order</field>
<field name="view_type">form</field> <field name="view_type">form</field>

View File

@ -1,5 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sale_shop,sale.shop,model_sale_shop,sale.group_sale_user,1,0,0,0 access_sale_shop,sale.shop,model_sale_shop,group_sale_user,1,0,0,0
access_sale_order,sale.order,model_sale_order,sale.group_sale_user,1,1,1,1 access_sale_order,sale.order,model_sale_order,group_sale_user,1,1,1,1
access_sale_order_line,sale.order.line,model_sale_order_line,sale.group_sale_user,1,1,1,1 access_sale_order_line,sale.order.line,model_sale_order_line,group_sale_user,1,1,1,1
access_sale_shop_admin,sale.shop,model_sale_shop,base.group_system,1,1,1,1 access_sale_shop_admin,sale.shop,model_sale_shop,base.group_system,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sale_shop sale.shop model_sale_shop sale.group_sale_user group_sale_user 1 0 0 0
3 access_sale_order sale.order model_sale_order sale.group_sale_user group_sale_user 1 1 1 1
4 access_sale_order_line sale.order.line model_sale_order_line sale.group_sale_user group_sale_user 1 1 1 1
5 access_sale_shop_admin sale.shop model_sale_shop base.group_system 1 1 1 1

View File

@ -30,6 +30,60 @@
from osv import fields, osv from osv import fields, osv
class product_product(osv.osv):
_inherit = "product.product"
# def view_header_get(self, cr, user, view_id, view_type, context):
# print self, cr, user
# res = super(product_product, self).view_header_get(cr, user, view_id, view_type, context)
# if res: return res
# if (not context.get('location', False)):
# return False
# cr.execute('select name from stock_location where id=%d', (context['location'],))
# j = cr.fetchone()[0]
# if j:
# return 'Products: '+j
# return False
#
def _get_product_available_func(states, what):
def _product_available(self, cr, uid, ids, name, arg, context={}):
if context.get('shop', False):
cr.execute('select warehouse_id from sale_shop where id=%d', (int(context['shop']),))
res2 = cr.fetchone()
if res2:
context['warehouse'] = res2[0]
if context.get('warehouse', False):
cr.execute('select lot_stock_id from stock_warehouse where id=%d', (int(context['warehouse']),))
res2 = cr.fetchone()
if res2:
context['location'] = res2[0]
if context.get('location', False):
location_ids = [context['location']]
else:
cr.execute("select lot_stock_id from stock_warehouse")
location_ids = [id for (id,) in cr.fetchall()]
# build the list of ids of children of the location given by id
location_ids = self.pool.get('stock.location').search(cr, uid, [('location_id', 'child_of', location_ids)])
res = self.pool.get('stock.location')._product_get_multi_location(cr, uid, location_ids, ids, context, states, what)
for id in ids:
res.setdefault(id, 0.0)
return res
return _product_available
_product_qty_available = _get_product_available_func(('done',), ('in', 'out'))
_product_virtual_available = _get_product_available_func(('confirmed','waiting','assigned','done'), ('in', 'out'))
_product_outgoing_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('out',))
_product_incoming_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('in',))
_columns = {
'qty_available': fields.function(_product_qty_available, method=True, type='float', string='Real Stock'),
'virtual_available': fields.function(_product_virtual_available, method=True, type='float', string='Virtual Stock'),
'incoming_qty': fields.function(_product_incoming_qty, method=True, type='float', string='Incoming'),
'outgoing_qty': fields.function(_product_outgoing_qty, method=True, type='float', string='Outgoing'),
}
product_product()
class product_product(osv.osv): class product_product(osv.osv):
_name = 'product.template' _name = 'product.template'

View File

@ -45,6 +45,7 @@
<field name="model">product.product</field> <field name="model">product.product</field>
<field name="type">form</field> <field name="type">form</field>
<field name="inherit_id" ref="product.product_normal_form_view"/> <field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="priority">26</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="property_account_expense" position="after"> <field name="property_account_expense" position="after">
<field name="property_stock_account_input"/> <field name="property_stock_account_input"/>

View File

@ -53,21 +53,6 @@ class stock_incoterms(osv.osv):
} }
stock_incoterms() stock_incoterms()
#class stock_lot(osv.osv):
# _name = "stock.lot"
# _description = "Lot"
# _columns = {
# 'name': fields.char('Lot Name', size=64, required=True),
# 'active': fields.boolean('Active'),
# 'tracking': fields.char('Tracking', size=64),
# 'move_ids': fields.one2many('stock.move', 'lot_id', 'Move lines'),
# }
# _defaults = {
# 'active': lambda *a: True,
# }
#stock_lot()
#---------------------------------------------------------- #----------------------------------------------------------
# Stock Location # Stock Location
#---------------------------------------------------------- #----------------------------------------------------------
@ -87,21 +72,30 @@ class stock_location(osv.osv):
'chained_location_id': fields.many2one('stock.location', 'Chained Location If Fixed'), 'chained_location_id': fields.many2one('stock.location', 'Chained Location If Fixed'),
'chained_location_type': fields.selection([('','None'),('customer', 'Customer'),('fixed','Fixed Location')], 'Chained Location Type'), 'chained_location_type': fields.selection([('','None'),('customer', 'Customer'),('fixed','Fixed Location')], 'Chained Location Type'),
'chained_auto_packing': fields.boolean('Chained Auto-Packing'), 'chained_auto_packing': fields.selection(
[('auto','Automatic Move'), ('manual','Manual Operation'),('transparent','Automatic No Step Added')],
'Automatic Move',
required=True, select=1,
help="This is used only if you selected a chained location type.\n" \
"The 'Automatic Move' value will create a stock move after the current one that will be "\
"validated automatically. With 'Manual Operation', the stock move has to be validated "\
"by a worker. With 'Automatic No Step Added', the location is replaced in the original move."
),
'chained_delay': fields.integer('Chained Delay (days)'), 'chained_delay': fields.integer('Chained Delay (days)'),
'address_id': fields.many2one('res.partner.address', 'Location Address'), 'address_id': fields.many2one('res.partner.address', 'Location Address'),
'comment': fields.text('Additional Information'), 'comment': fields.text('Additional Information'),
'posx': fields.integer('Corridor (X)', required=True), 'posx': fields.integer('Corridor (X)'),
'posy': fields.integer('Shelves (Y)', required=True), 'posy': fields.integer('Shelves (Y)'),
'posz': fields.integer('Height (Z)', required=True), 'posz': fields.integer('Height (Z)'),
} }
_defaults = { _defaults = {
'active': lambda *a: 1, 'active': lambda *a: 1,
'usage': lambda *a: 'internal', 'usage': lambda *a: 'internal',
'allocation_method': lambda *a: 'fifo', 'allocation_method': lambda *a: 'fifo',
'chained_location_type': lambda *a: '', 'chained_location_type': lambda *a: '',
'chained_auto_packing': lambda *a: 'manual',
'posx': lambda *a: 0, 'posx': lambda *a: 0,
'posy': lambda *a: 0, 'posy': lambda *a: 0,
'posz': lambda *a: 0, 'posz': lambda *a: 0,
@ -110,12 +104,14 @@ class stock_location(osv.osv):
def chained_location_get(self, cr, uid, location, partner=None, product=None, context={}): def chained_location_get(self, cr, uid, location, partner=None, product=None, context={}):
result = None result = None
if location.chained_location_type=='customer': if location.chained_location_type=='customer':
result = partner.property_stock_customer if partner:
result = partner.property_stock_customer
elif location.chained_location_type=='fixed': elif location.chained_location_type=='fixed':
result = location.chained_location_id result = location.chained_location_id
if result:
return result, location.chained_auto_packing, location.chained_delay
return result return result
def picking_type_get(self, cr, uid, from_location, to_location, context={}): def picking_type_get(self, cr, uid, from_location, to_location, context={}):
result = 'internal' result = 'internal'
if (from_location.usage=='internal') and (to_location and to_location.usage in ('customer','supplier')): if (from_location.usage=='internal') and (to_location and to_location.usage in ('customer','supplier')):
@ -175,9 +171,7 @@ class stock_location(osv.osv):
states_str = ','.join(map(lambda s: "'%s'" % s, states)) states_str = ','.join(map(lambda s: "'%s'" % s, states))
if not product_ids: if not product_ids:
product_ids = product_obj.search(cr, uid, []) product_ids = product_obj.search(cr, uid, [])
res = {} res = {}.fromkeys(product_ids, 0.0)
for id in product_ids:
res[id] = 0.0
if not ids: if not ids:
return res return res
@ -332,15 +326,14 @@ class stock_picking(osv.osv):
_name = "stock.picking" _name = "stock.picking"
_description = "Packing list" _description = "Packing list"
_columns = { _columns = {
'name': fields.char('Packing name', size=64, required=True, select=True), 'name': fields.char('Reference', size=64, required=True, select=True),
'origin': fields.char('Origin', size=64), 'origin': fields.char('Origin', size=64),
'type': fields.selection([('out','Sending Goods'),('in','Getting Goods'),('internal','Internal'),('delivery','Delivery')], 'Shipping Type', required=True, select=True), 'type': fields.selection([('out','Sending Goods'),('in','Getting Goods'),('internal','Internal'),('delivery','Delivery')], 'Shipping Type', required=True, select=True),
'active': fields.boolean('Active'), 'active': fields.boolean('Active'),
'note': fields.text('Notes'), 'note': fields.text('Notes'),
'location_id': fields.many2one('stock.location', 'Location'), #'location_id': fields.many2one('stock.location', 'Location'),
'location_dest_id': fields.many2one('stock.location', 'Dest. Location'), #'location_dest_id': fields.many2one('stock.location', 'Dest. Location'),
'move_type': fields.selection([('direct','Direct Delivery'),('one','All at once')],'Delivery Method', required=True), 'move_type': fields.selection([('direct','Direct Delivery'),('one','All at once')],'Delivery Method', required=True),
'state': fields.selection([ 'state': fields.selection([
('draft','Draft'), ('draft','Draft'),
@ -351,9 +344,7 @@ class stock_picking(osv.osv):
('cancel','Cancel'), ('cancel','Cancel'),
], 'Status', readonly=True, select=True), ], 'Status', readonly=True, select=True),
'date':fields.datetime('Date Create'), 'date':fields.datetime('Date Create'),
'move_lines': fields.one2many('stock.move', 'picking_id', 'Move Lines'), 'move_lines': fields.one2many('stock.move', 'picking_id', 'Move Lines'),
'auto_picking': fields.boolean('Auto-Packing'), 'auto_picking': fields.boolean('Auto-Packing'),
'address_id': fields.many2one('res.partner.address', 'Partner'), 'address_id': fields.many2one('res.partner.address', 'Partner'),
'invoice_state':fields.selection([ 'invoice_state':fields.selection([
@ -363,7 +354,7 @@ class stock_picking(osv.osv):
select=True), select=True),
} }
_defaults = { _defaults = {
'name': lambda *a: '/', 'name': lambda self,cr,uid,context: self.pool.get('ir.sequence').get(cr, uid, 'stock.picking'),
'active': lambda *a: 1, 'active': lambda *a: 1,
'state': lambda *a: 'draft', 'state': lambda *a: 'draft',
'move_type': lambda *a: 'direct', 'move_type': lambda *a: 'direct',
@ -371,64 +362,28 @@ class stock_picking(osv.osv):
'invoice_state': lambda *a: 'none', 'invoice_state': lambda *a: 'none',
'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
} }
def copy(self, cr, uid, id, data=None, context={}):
data = data or {}
data['name'] = '/'
return super(stock_picking, self).copy(cr, uid, id, data, context)
def onchange_partner_in(self, cr, uid, context, partner_id=None): def onchange_partner_in(self, cr, uid, context, partner_id=None):
sid = self.pool.get('res.partner.address').browse(cr, uid, partner_id, context).partner_id.property_stock_supplier.id sid = self.pool.get('res.partner.address').browse(cr, uid, partner_id, context).partner_id.property_stock_supplier.id
return { return { }
'value': {'location_id':sid}
}
def action_explode(self, cr, uid, ids, *args): def action_explode(self, cr, uid, moves, context={}):
return True return moves
def action_confirm(self, cr, uid, ids, *args): def action_confirm(self, cr, uid, ids, context={}):
self.write(cr, uid, ids, {'state': 'confirmed'}) self.write(cr, uid, ids, {'state': 'confirmed'})
todo = [] todo = []
for picking in self.browse(cr, uid, ids): for picking in self.browse(cr, uid, ids):
if picking.name == self._defaults['name']():
number = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.%s' % picking.type)
self.write(cr, uid, [picking.id], {'name': number})
for r in picking.move_lines: for r in picking.move_lines:
if r.state=='draft': if r.state=='draft':
todo.append(r.id) todo.append(r)
todo = self.action_explode(cr, uid, todo, context)
if len(todo): if len(todo):
self.pool.get('stock.move').action_confirm(cr,uid, todo) self.pool.get('stock.move').action_confirm(cr,uid, todo, context)
self.action_explode(cr, uid, ids)
for picking in self.browse(cr, uid, ids):
todo = []
todo.extend( filter( lambda r: r.location_dest_id.chained_location_type, picking.move_lines) )
if todo:
loc = self.pool.get('stock.location').chained_location_get(cr, uid, todo[0].location_dest_id, todo[0].picking_id and todo[0].picking_id.address_id and todo[0].picking_id.address_id.partner_id, todo[0].product_id)
ptype = self.pool.get('stock.location').picking_type_get(cr, uid, todo[0].location_dest_id, loc)
pickid = self.pool.get('stock.picking').create(cr, uid, {
'name': picking.name,
'origin': str(picking.origin or ''),
'type': ptype,
'note': picking.note,
'move_type': picking.move_type,
'auto_picking': todo[0].location_dest_id.chained_auto_packing,
'address_id': picking.address_id.id,
'invoice_state': 'none'
})
for move in todo:
loc = self.pool.get('stock.location').chained_location_get(cr, uid, move.location_dest_id, picking.address_id.partner_id, move.product_id).id
new_id = self.pool.get('stock.move').copy(cr, uid, move.id, {
'location_id': move.location_dest_id.id,
'location_dest_id': loc,
'date_moved': time.strftime('%Y-%m-%d'),
'picking_id': pickid,
'state':'waiting',
'prodlot_id':False,
'tracking_id':False,
'move_history_ids':[],
'date_planned': DateTime.strptime(move.date_planned, '%Y-%m-%d') + DateTime.RelativeDateTime(days=move.location_dest_id.chained_delay or 0),
'move_history_ids2':[]}
)
self.pool.get('stock.move').write(cr, uid, [move.id], {
'move_dest_id': new_id,
'move_history_ids': [(4, new_id)]
})
wf_service = netsvc.LocalService("workflow")
wf_service.trg_validate(uid, 'stock.picking', pickid, 'button_confirm', cr)
return True return True
def test_auto_picking(self, cr, uid, ids): def test_auto_picking(self, cr, uid, ids):
@ -762,7 +717,23 @@ class stock_move(osv.osv):
'price_unit': fields.float('Unit Price', 'price_unit': fields.float('Unit Price',
digits=(16, int(config['price_accuracy']))), digits=(16, int(config['price_accuracy']))),
} }
def _default_location_destination(self, cr, uid, context={}):
if context.get('move_line', []):
return context['move_line'][0][2]['location_dest_id']
if context.get('address_out_id', False):
return self.pool.get('res.partner.address').browse(cr, uid, context['address_out_id'], context).partner_id.property_stock_customer.id
return False
def _default_location_source(self, cr, uid, context={}):
if context.get('move_line', []):
return context['move_line'][0][2]['location_id']
if context.get('address_in_id', False):
return self.pool.get('res.partner.address').browse(cr, uid, context['address_in_id'], context).partner_id.property_stock_supplier.id
return False
_defaults = { _defaults = {
'location_id': _default_location_source,
'location_dest_id': _default_location_destination,
'state': lambda *a: 'draft', 'state': lambda *a: 'draft',
'priority': lambda *a: '1', 'priority': lambda *a: '1',
'product_qty': lambda *a: 1.0, 'product_qty': lambda *a: 1.0,
@ -795,9 +766,55 @@ class stock_move(osv.osv):
result['location_dest_id'] = loc_dest_id result['location_dest_id'] = loc_dest_id
return {'value':result} return {'value':result}
def action_confirm(self, cr, uid, ids, context={}): def _chain_compute(self, cr, uid, moves, context={}):
result = {}
for m in moves:
dest = self.pool.get('stock.location').chained_location_get(cr, uid, m.location_dest_id, m.picking_id and m.picking_id.address_id and m.picking_id.address_id.partner_id, m.product_id, context)
if dest:
if dest[1]=='transparent':
self.write(cr, uid, [m.id], {
'date_planned': (DateTime.strptime(m.date_planned, '%Y-%m-%d') + \
DateTime.RelativeDateTime(days=dest[2] or 0)).strftime('%Y-%m-%d'),
'location_dest_id': dest[0].id})
else:
result.setdefault(m.picking_id, [])
result[m.picking_id].append( (m, dest) )
return result
def action_confirm(self, cr, uid, moves, context={}):
ids = map(lambda m: m.id, moves)
self.write(cr, uid, ids, {'state':'confirmed'}) self.write(cr, uid, ids, {'state':'confirmed'})
return True for picking, todo in self._chain_compute(cr, uid, moves, context).items():
ptype = self.pool.get('stock.location').picking_type_get(cr, uid, todo[0][0].location_dest_id, todo[0][1][0])
pickid = self.pool.get('stock.picking').create(cr, uid, {
'name': picking.name,
'origin': str(picking.origin or ''),
'type': ptype,
'note': picking.note,
'move_type': picking.move_type,
'auto_picking': todo[0][1][1]=='auto',
'address_id': picking.address_id.id,
'invoice_state': 'none'
})
for move,(loc,auto,delay) in todo:
# Is it smart to copy ? May be it's better to recreate ?
new_id = self.pool.get('stock.move').copy(cr, uid, move.id, {
'location_id': move.location_dest_id.id,
'location_dest_id': loc.id,
'date_moved': time.strftime('%Y-%m-%d'),
'picking_id': pickid,
'state':'waiting',
'move_history_ids':[],
'date_planned': (DateTime.strptime(move.date_planned, '%Y-%m-%d') + DateTime.RelativeDateTime(days=delay or 0)).strftime('%Y-%m-%d'),
'move_history_ids2':[]}
)
self.pool.get('stock.move').write(cr, uid, [move.id], {
'move_dest_id': new_id,
'move_history_ids': [(4, new_id)]
})
wf_service = netsvc.LocalService("workflow")
wf_service.trg_validate(uid, 'stock.picking', pickid, 'button_confirm', cr)
return []
def action_assign(self, cr, uid, ids, *args): def action_assign(self, cr, uid, ids, *args):
todo = [] todo = []
@ -1092,63 +1109,6 @@ class stock_warehouse(osv.osv):
stock_warehouse() stock_warehouse()
# Product
class product_product(osv.osv):
_inherit = "product.product"
#
# Utiliser browse pour limiter les queries !
#
def view_header_get(self, cr, user, view_id, view_type, context):
res = super(product_product, self).view_header_get(cr, user, view_id, view_type, context)
if res: return res
if (not context.get('location', False)):
return False
cr.execute('select name from stock_location where id=%d', (context['location'],))
j = cr.fetchone()[0]
if j:
return 'Products: '+j
return False
def _get_product_available_func(states, what):
def _product_available(self, cr, uid, ids, name, arg, context={}):
if context.get('shop', False):
cr.execute('select warehouse_id from sale_shop where id=%d', (int(context['shop']),))
res2 = cr.fetchone()
if res2:
context['warehouse'] = res2[0]
if context.get('warehouse', False):
cr.execute('select lot_stock_id from stock_warehouse where id=%d', (int(context['warehouse']),))
res2 = cr.fetchone()
if res2:
context['location'] = res2[0]
if context.get('location', False):
location_ids = [context['location']]
else:
# get the list of ids of the stock location of all warehouses
cr.execute("select lot_stock_id from stock_warehouse")
location_ids = [id for (id,) in cr.fetchall()]
# build the list of ids of children of the location given by id
location_ids = self.pool.get('stock.location').search(cr, uid, [('location_id', 'child_of', location_ids)])
res = self.pool.get('stock.location')._product_get_multi_location(cr, uid, location_ids, ids, context, states, what)
for id in ids:
res.setdefault(id, 0.0)
return res
return _product_available
_product_qty_available = _get_product_available_func(('done',), ('in', 'out'))
_product_virtual_available = _get_product_available_func(('confirmed','waiting','assigned','done'), ('in', 'out'))
_product_outgoing_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('out',))
_product_incoming_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('in',))
_columns = {
'qty_available': fields.function(_product_qty_available, method=True, type='float', string='Real Stock'),
'virtual_available': fields.function(_product_virtual_available, method=True, type='float', string='Virtual Stock'),
'incoming_qty': fields.function(_product_incoming_qty, method=True, type='float', string='Incoming'),
'outgoing_qty': fields.function(_product_outgoing_qty, method=True, type='float', string='Outgoing'),
}
product_product()
# Move wizard : # Move wizard :
# get confirm or assign stock move lines of partner and put in current picking. # get confirm or assign stock move lines of partner and put in current picking.
class stock_picking_move_wizard(osv.osv_memory): class stock_picking_move_wizard(osv.osv_memory):

View File

@ -1,42 +1,24 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<terp> <terp>
<data noupdate="1"> <data>
<!-- <!--
Sequences for packings Sequences for packings
--> -->
<record id="seq_type_picking_out" model="ir.sequence.type"> <record id="seq_type_picking" model="ir.sequence.type">
<field name="name">Packing Out</field> <field name="name">Packing</field>
<field name="code">stock.picking.out</field> <field name="code">stock.picking</field>
</record> </record>
<record id="seq_type_picking_in" model="ir.sequence.type">
<field name="name">Packing In</field> <record id="seq_picking" model="ir.sequence">
<field name="code">stock.picking.in</field> <field name="name">Packing</field>
</record> <field name="code">stock.picking</field>
<record id="seq_type_picking_internal" model="ir.sequence.type"> <field name="prefix">PACK</field>
<field name="name">Packing Internal</field>
<field name="code">stock.picking.internal</field>
</record>
<record id="seq_picking_out" model="ir.sequence">
<field name="name">Packing Out</field>
<field name="code">stock.picking.out</field>
<field name="prefix">OUT:</field>
</record>
<record id="seq_picking_in" model="ir.sequence">
<field name="name">Packing In</field>
<field name="code">stock.picking.in</field>
<field name="prefix">IN:</field>
</record>
<record id="seq_picking_internal" model="ir.sequence">
<field name="name">Packing Internal</field>
<field name="code">stock.picking.internal</field>
<field name="prefix">INTERNAL:</field>
</record> </record>
<!-- <!--
Sequences from tracking numbers Sequences from tracking numbers
--> -->
<record id="sequence_type_serial" model="ir.sequence.type"> <record id="sequence_type_serial" model="ir.sequence.type">
<field name="name">Stock Production Lots</field> <field name="name">Stock Production Lots</field>
<field name="code">stock.lot.serial</field> <field name="code">stock.lot.serial</field>
@ -44,7 +26,7 @@
<record id="sequence_production_lots" model="ir.sequence"> <record id="sequence_production_lots" model="ir.sequence">
<field name="name">Stock Production Lots</field> <field name="name">Stock Production Lots</field>
<field name="code">stock.lot.serial</field> <field name="code">stock.lot.serial</field>
<field name="prefix">1234567890</field> <field name="prefix"></field>
<field name="padding">7</field> <field name="padding">7</field>
<field name="number_next">1</field> <field name="number_next">1</field>
<field name="number_increment">1</field> <field name="number_increment">1</field>
@ -59,10 +41,10 @@
<record id="sequence_tracking" model="ir.sequence"> <record id="sequence_tracking" model="ir.sequence">
<field name="name">Stock Tracking Lots</field> <field name="name">Stock Tracking Lots</field>
<field name="code">stock.lot.tracking</field> <field name="code">stock.lot.tracking</field>
<field name="prefix">1234567890</field> <field name="prefix"></field>
<field name="padding">7</field> <field name="padding">7</field>
<field name="number_next">1</field> <field name="number_next">1</field>
<field name="number_increment">1</field> <field name="number_increment">1</field>
</record> </record>
</data> </data>
</terp> </terp>

View File

@ -28,8 +28,8 @@
<form string="Stock Inventory Lines"> <form string="Stock Inventory Lines">
<field colspan="4" domain="[('usage','=','internal')]" name="location_id" select="1"/> <field colspan="4" domain="[('usage','=','internal')]" name="location_id" select="1"/>
<field context="location=location_id,uom=product_uom" name="product_id" on_change="on_change_product_id(location_id,product_id,product_uom)" select="1"/> <field context="location=location_id,uom=product_uom" name="product_id" on_change="on_change_product_id(location_id,product_id,product_uom)" select="1"/>
<field name="product_uom"/>
<field name="product_qty"/> <field name="product_qty"/>
<field name="product_uom"/>
</form> </form>
</field> </field>
</record> </record>
@ -313,7 +313,7 @@
<field name="usage" select="1"/> <field name="usage" select="1"/>
<field name="account_id" select="1"/> <field name="account_id" select="1"/>
<field name="location_id"/> <field name="location_id"/>
<field name="address_id"/> <field name="address_id" context="{'context_display':'partner'}"/>
<group col="2" colspan="2"> <group col="2" colspan="2">
<separator string="Chained Locations" colspan="2"/> <separator string="Chained Locations" colspan="2"/>
<field name="chained_location_type"/> <field name="chained_location_type"/>
@ -391,7 +391,7 @@
<field name="lot_stock_id"/> <field name="lot_stock_id"/>
<field name="lot_output_id"/> <field name="lot_output_id"/>
<newline/> <newline/>
<field name="partner_address_id"/> <field name="partner_address_id" context="{'context_display':'partner'}"/>
</form> </form>
</field> </field>
</record> </record>
@ -405,7 +405,7 @@
<field name="lot_input_id"/> <field name="lot_input_id"/>
<field name="lot_stock_id"/> <field name="lot_stock_id"/>
<field name="lot_output_id"/> <field name="lot_output_id"/>
<field name="partner_address_id"/> <field name="partner_address_id" context="{'context_display':'partner'}"/>
</tree> </tree>
</field> </field>
</record> </record>
@ -427,7 +427,7 @@
<field name="type">form</field> <field name="type">form</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Move Lines"> <form string="Move Lines">
<field name="address_id" invisible="True"/> <field name="address_id" invisible="True" context="{'context_display':'partner'}"/>
<field name="picking_id" invisible="True"/> <field name="picking_id" invisible="True"/>
<field domain="[('picking_id','&lt;&gt;',picking_id),('state','in',['confirmed','assigned'])]" name="move_ids" select="1"/><newline/> <field domain="[('picking_id','&lt;&gt;',picking_id),('state','in',['confirmed','assigned'])]" name="move_ids" select="1"/><newline/>
<group colspan="4"> <group colspan="4">
@ -461,7 +461,7 @@
<tree color="red:state=='cancel'" string="Packing list"> <tree color="red:state=='cancel'" string="Packing list">
<field colspan="4" name="name" select="1"/> <field colspan="4" name="name" select="1"/>
<field name="date" select="1"/> <field name="date" select="1"/>
<field name="address_id" select="1"/> <field name="address_id" select="1" context="{'context_display':'partner'}"/>
<field name="invoice_state" readonly="1"/> <field name="invoice_state" readonly="1"/>
<field name="origin" select="1"/> <field name="origin" select="1"/>
<field name="state" readonly="1"/> <field name="state" readonly="1"/>
@ -476,37 +476,31 @@
<form string="Packing list"> <form string="Packing list">
<notebook> <notebook>
<page string="General Information"> <page string="General Information">
<field name="name" select="1"/> <field name="address_id" select="2" context="{'context_display':'partner'}"/>
<field name="type" select="2" readonly="1"/>
<field name="name" select="1" readonly="1"/>
<field name="date" select="1"/> <field name="date" select="1"/>
<newline/> <newline/>
<field name="type" select="2"/> <field colspan="4" name="move_lines" nolabel="1" widget="one2many_list" default_get="{'move_line':move_lines, 'address_out_id': address_id}">
<field name="invoice_state" select="2"/>
<newline/>
<field name="location_id"/>
<field name="location_dest_id"/>
<field name="address_id" select="2"/>
<field colspan="4" name="move_lines" nolabel="1" widget="one2many_list">
<form string="Stock Moves"> <form string="Stock Moves">
<separator colspan="4" string="Move Information"/> <separator colspan="4" string="Move Information"/>
<field colspan="4" context="location=parent.location_id" name="product_id" on_change="onchange_product_id(product_id, parent.location_id, parent.location_dest_id)" select="1"/> <field name="location_id" select="1" domain="[('usage','=','internal')]"/>
<field name="product_uom" select="1"/> <field name="location_dest_id" select="1" domain="[('usage','=','internal')]"/>
<field colspan="4" context="location=location_id" name="product_id" on_change="onchange_product_id(product_id, location_id, location_dest_id)" select="1"/>
<field name="product_qty" select="1"/> <field name="product_qty" select="1"/>
<field name="product_uom" select="1"/>
<field groups="product.group_uos" name="product_uos"/> <field groups="product.group_uos" name="product_uos"/>
<field groups="product.group_uos" name="product_uos_qty"/> <field groups="product.group_uos" name="product_uos_qty"/>
<field colspan="4" name="name" select="1"/> <field colspan="4" invisible="1" name="name" select="1"/>
<field name="date"/> <field invisible="1" name="date"/>
<field name="date_planned"/> <field name="date_planned"/>
<field name="priority"/> <field name="address_id" select="1" context="{'context_display':'partner'}"/>
<field name="location_id" select="1"/> <field groups="base.group_extended" name="product_packaging"/>
<field name="location_dest_id" select="1"/>
<field name="address_id" select="1"/>
<field name="product_packaging"/>
<field context="product_id=product_id" name="prodlot_id" select="1"/> <field context="product_id=product_id" name="prodlot_id" select="1"/>
<field name="tracking_id" select="1"/> <field groups="base.group_extended" name="tracking_id" select="1"/>
<newline/> <newline/>
<label/> <label/>
<button name="%(track_line)d" string="Split in production lots" type="action"/> <button name="%(track_line)d" string="Split in production lots" type="action"/>
<separator colspan="4" string="Move State"/> <separator colspan="4" string="Move State"/>
<field name="state" select="1"/> <field name="state" select="1"/>
<group> <group>
@ -556,7 +550,7 @@
<tree color="red:state=='cancel'" string="Packing list"> <tree color="red:state=='cancel'" string="Packing list">
<field colspan="4" name="name" select="1"/> <field colspan="4" name="name" select="1"/>
<field name="date" select="1"/> <field name="date" select="1"/>
<field name="address_id" select="1"/> <field name="address_id" select="1" context="{'context_display':'partner'}"/>
<field name="origin" select="1"/> <field name="origin" select="1"/>
<field name="state" readonly="1"/> <field name="state" readonly="1"/>
</tree> </tree>
@ -568,26 +562,25 @@
<field name="type">form</field> <field name="type">form</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Packing list"> <form string="Packing list">
<field name="name" select="1"/> <field name="address_id" select="2" context="{'context_display':'partner'}"/>
<field name="date" select="1"/>
<newline/>
<field name="address_id" select="2"/>
<field name="origin" select="2"/> <field name="origin" select="2"/>
<field name="name" select="1" readonly="1"/>
<field name="date" select="1" readonly="1"/>
<notebook colspan="4"> <notebook colspan="4">
<page string="General Information"> <page string="General Information">
<field colspan="4" name="move_lines" nolabel="1" widget="one2many_list"> <field colspan="4" name="move_lines" nolabel="1" widget="one2many_list" default_get="{'move_line':move_lines, 'address_out_id': address_id}">
<form string="Stock Moves"> <form string="Stock Moves">
<separator colspan="4" string="Move Information"/> <separator colspan="4" string="Move Information"/>
<field colspan="4" context="location=parent.location_id" name="product_id" on_change="onchange_product_id(product_id, parent.location_id, parent.location_dest_id)" select="1"/> <field name="location_id" select="1" domain="[('usage','=','internal')]"/>
<field name="product_uom" select="1"/> <field name="location_dest_id" select="1" domain="[('usage','&lt;&gt;','view')]"/>
<field colspan="4" context="location=location_id" name="product_id" on_change="onchange_product_id(product_id, location_id, location_dest_id)" select="1"/>
<field name="product_qty" select="1"/> <field name="product_qty" select="1"/>
<field name="product_uom" select="1"/>
<field groups="product.group_uos" name="product_uos"/> <field groups="product.group_uos" name="product_uos"/>
<field groups="product.group_uos" name="product_uos_qty"/> <field groups="product.group_uos" name="product_uos_qty"/>
<field colspan="4" invisible="1" name="name" select="1"/> <field colspan="4" invisible="1" name="name" select="1"/>
<field invisible="1" name="date"/> <field invisible="1" name="date"/>
<field name="date_planned"/> <field name="date_planned"/>
<field name="location_id" select="1"/>
<field name="location_dest_id" select="1"/>
<field groups="base.group_extended" name="product_packaging"/> <field groups="base.group_extended" name="product_packaging"/>
<field context="product_id=product_id" name="prodlot_id" select="1"/> <field context="product_id=product_id" name="prodlot_id" select="1"/>
<field groups="base.group_extended" name="tracking_id" select="1"/> <field groups="base.group_extended" name="tracking_id" select="1"/>
@ -710,7 +703,7 @@
<tree color="red:state=='cancel'" string="Packing list"> <tree color="red:state=='cancel'" string="Packing list">
<field colspan="4" name="name" select="1"/> <field colspan="4" name="name" select="1"/>
<field name="date" select="1"/> <field name="date" select="1"/>
<field name="address_id" select="1"/> <field name="address_id" select="1" context="{'context_display':'partner'}"/>
<field name="invoice_state" readonly="1"/> <field name="invoice_state" readonly="1"/>
<field name="origin" select="1"/> <field name="origin" select="1"/>
<field name="state" readonly="1"/> <field name="state" readonly="1"/>
@ -723,26 +716,25 @@
<field name="type">form</field> <field name="type">form</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Packing list"> <form string="Packing list">
<field name="name" select="1"/> <field name="address_id" select="2" context="{'context_display':'partner'}"/>
<field name="date" select="1"/> <field name="date" select="1" readonly="1"/>
<newline/>
<field name="address_id" select="2"/>
<field name="origin" select="2"/> <field name="origin" select="2"/>
<field name="name" select="1" readonly="1"/>
<notebook colspan="4"> <notebook colspan="4">
<page string="General Information"> <page string="General Information">
<field colspan="4" name="move_lines" nolabel="1" widget="one2many_list"> <field colspan="4" name="move_lines" nolabel="1" widget="one2many_list" default_get="{'move_line':move_lines, 'address_out_id': address_id}">
<form string="Stock Moves"> <form string="Stock Moves">
<separator colspan="4" string="Move Information"/> <separator colspan="4" string="Move Information"/>
<field colspan="4" context="location=parent.location_id" name="product_id" on_change="onchange_product_id(product_id, parent.location_id, parent.location_dest_id)" select="1"/> <field name="location_id" select="1" domain="[('usage','=','internal')]"/>
<field name="product_uom" select="1"/> <field name="location_dest_id" select="1" domain="[('usage','&lt;&gt;','view')]"/>
<field colspan="4" context="location=location_id" name="product_id" on_change="onchange_product_id(product_id, location_id, location_dest_id)" select="1"/>
<field name="product_qty" select="1"/> <field name="product_qty" select="1"/>
<field name="product_uom" select="1"/>
<field groups="product.group_uos" name="product_uos"/> <field groups="product.group_uos" name="product_uos"/>
<field groups="product.group_uos" name="product_uos_qty"/> <field groups="product.group_uos" name="product_uos_qty"/>
<field colspan="4" invisible="1" name="name" select="1"/> <field colspan="4" invisible="1" name="name" select="1"/>
<field invisible="1" name="date"/> <field invisible="1" name="date"/>
<field name="date_planned"/> <field name="date_planned"/>
<field name="location_id" select="1"/>
<field name="location_dest_id" select="1"/>
<field groups="base.group_extended" name="product_packaging"/> <field groups="base.group_extended" name="product_packaging"/>
<field context="product_id=product_id" name="prodlot_id" select="1"/> <field context="product_id=product_id" name="prodlot_id" select="1"/>
<field groups="base.group_extended" name="tracking_id" select="1"/> <field groups="base.group_extended" name="tracking_id" select="1"/>
@ -862,7 +854,7 @@
<tree color="red:state=='cancel'" string="Packing list"> <tree color="red:state=='cancel'" string="Packing list">
<field colspan="4" name="name" select="1"/> <field colspan="4" name="name" select="1"/>
<field name="date" select="1"/> <field name="date" select="1"/>
<field name="address_id" select="1"/> <field name="address_id" select="1" context="{'context_display':'partner'}"/>
<field name="invoice_state" readonly="1"/> <field name="invoice_state" readonly="1"/>
<field name="origin" select="1"/> <field name="origin" select="1"/>
<field name="state" readonly="1"/> <field name="state" readonly="1"/>
@ -875,15 +867,13 @@
<field name="type">form</field> <field name="type">form</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Input Packing List"> <form string="Input Packing List">
<field name="address_id" on_change="onchange_partner_in(address_id)" select="2"/> <field name="address_id" on_change="onchange_partner_in(address_id)" select="2" context="{'context_display':'partner'}"/>
<field name="name" readonly="1" select="1"/>
<field name="location_id"/>
<field domain="[('usage','=','internal')]" name="location_dest_id"/>
<field name="invoice_state" select="2" string="Supplier Invoice Control"/>
<field name="origin" select="2"/> <field name="origin" select="2"/>
<field name="invoice_state" select="2" string="Invoice Control"/>
<field name="name" readonly="1" select="1"/>
<notebook colspan="4"> <notebook colspan="4">
<page string="General Information"> <page string="General Information">
<field colspan="4" name="move_lines" nolabel="1" widget="one2many_list"> <field colspan="4" name="move_lines" nolabel="1" widget="one2many_list" default_get="{'move_line':move_lines, 'address_in_id': address_id}">
<tree string="Stock Moves"> <tree string="Stock Moves">
<field name="product_id"/> <field name="product_id"/>
<field name="product_qty"/> <field name="product_qty"/>
@ -894,17 +884,17 @@
</tree> </tree>
<form string="Stock Moves"> <form string="Stock Moves">
<separator colspan="4" string="Move Information"/> <separator colspan="4" string="Move Information"/>
<field colspan="4" context="location=parent.location_id" name="product_id" on_change="onchange_product_id(product_id, parent.location_id, parent.location_dest_id)" select="1"/> <field name="location_id" select="1" domain="[('usage','&lt;&gt;','view')]"/>
<field name="product_uom" select="1"/> <field domain="[('usage','=','internal')]" name="location_dest_id" select="1"/>
<field colspan="4" context="location=location_id" name="product_id" on_change="onchange_product_id(product_id, location_id, location_dest_id)" select="1"/>
<field name="product_qty" select="1"/> <field name="product_qty" select="1"/>
<field name="product_uom" select="1"/>
<field groups="product.group_uos" name="product_uos"/> <field groups="product.group_uos" name="product_uos"/>
<field groups="product.group_uos" name="product_uos_qty"/> <field groups="product.group_uos" name="product_uos_qty"/>
<field colspan="4" invisible="1" name="name" select="1"/> <field colspan="4" invisible="1" name="name" select="1"/>
<field groups="base.group_extended" name="date"/> <field groups="base.group_extended" name="date"/>
<field groups="base.group_extended" name="date_planned"/> <field groups="base.group_extended" name="date_planned"/>
<newline/> <newline/>
<field name="location_id" select="1"/>
<field domain="[('usage','=','internal')]" name="location_dest_id" select="1"/>
<newline/> <newline/>
<field groups="base.group_extended" name="product_packaging"/> <field groups="base.group_extended" name="product_packaging"/>
<newline/> <newline/>
@ -1099,16 +1089,16 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Stock Moves"> <form string="Stock Moves">
<separator colspan="4" string="Move Information"/> <separator colspan="4" string="Move Information"/>
<field name="location_id" select="1"/>
<field name="location_dest_id" select="1"/>
<field colspan="4" name="product_id" select="1"/>
<field name="product_qty" select="1"/>
<field name="product_uom" select="1"/>
<field colspan="4" name="name" select="1"/> <field colspan="4" name="name" select="1"/>
<field name="date"/> <field name="date"/>
<field name="date_planned"/> <field name="date_planned"/>
<field colspan="4" name="product_id" select="1"/>
<field name="product_uom" select="1"/>
<field name="product_qty" select="1"/>
<field name="location_id" select="1"/>
<field name="location_dest_id" select="1"/>
<field name="priority"/> <field name="priority"/>
<field name="address_id" select="1"/> <field name="address_id" select="1" context="{'context_display':'partner'}"/>
<newline/> <newline/>
<field context="product_id=product_id" name="prodlot_id" select="1"/> <field context="product_id=product_id" name="prodlot_id" select="1"/>
<field name="tracking_id" select="1"/> <field name="tracking_id" select="1"/>

View File

@ -0,0 +1,31 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
# Fabien Pinckaers <fp@tiny.Be>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import stock

View File

@ -0,0 +1,25 @@
{
"name" : "Stock Location Paths",
"version" : "1.0",
"author" : "Tiny",
"depends" : [ "stock"],
"category" : "Generic Modules/Inventory Control",
"description":"""
Manages product's path in locations.
This module may be usefull for different purposes:
* Manages the product in his whole manufacturing chain
* Manages different default locations by products
* Define paths within the warehouse to route products based on operations:
- Quality Control
- After Sales Services
- Supplier Return
* Manage products to be rent.
""",
"init_xml" : [],
"demo_xml" : [],
"update_xml" : ["stock_view.xml"],
"active": False,
"installable": True
}

View File

@ -0,0 +1,77 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
# Fabien Pinckaers <fp@tiny.Be>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from osv import fields,osv
import tools
import ir
import pooler
class stock_location_path(osv.osv):
_name = "stock.location.path"
_columns = {
'name': fields.char('Operation', size=64),
'product_id' : fields.many2one('product.product', 'Products', ondelete='cascade', select=1),
'location_from_id' : fields.many2one('stock.location', 'Source Location', ondelete='cascade', select=1),
'location_dest_id' : fields.many2one('stock.location', 'Destination Location', ondelete='cascade', select=1),
'delay': fields.integer('Delay (days)', help="Number of days to do this transition"),
'auto': fields.selection(
[('auto','Automatic Move'), ('manual','Manual Operation'),('transparent','Automatic No Step Added')],
'Automatic Move',
required=True, select=1,
help="This is used to define paths the product has to follow within the location tree.\n" \
"The 'Automatic Move' value will create a stock move after the current one that will be "\
"validated automatically. With 'Manual Operation', the stock move has to be validated "\
"by a worker. With 'Automatic No Step Added', the location is replaced in the original move."
),
}
_defaults = {
'auto': lambda *arg: 'auto',
'delay': lambda *arg: 1
}
stock_location_path()
class product_product(osv.osv):
_inherit = 'product.product'
_columns = {
'path_ids': fields.one2many('stock.location.path', 'product_id',
'Location Paths',
help="These rules set the right path of the product in the "\
"whole location tree.")
}
product_product()
class stock_location(osv.osv):
_inherit = 'stock.location'
def chained_location_get(self, cr, uid, location, partner=None, product=None, context={}):
if product:
for path in product.path_ids:
if path.location_from_id.id == location.id:
return path.location_dest_id, path.auto, path.delay
return super(stock_location, self).chained_location_get(cr, uid, location, partner, product, contex)
stock_location()

View File

@ -0,0 +1,58 @@
<?xml version="1.0"?>
<terp>
<data>
<record id="stock_location_path_tree" model="ir.ui.view">
<field name="name">stock.location.path.tree</field>
<field name="model">stock.location.path</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Location Paths">
<field name="product_id"/>
<field name="location_from_id"/>
<field name="location_dest_id"/>
<field name="name"/>
</tree>
</field>
</record>
<record id="stock_location_path_form" model="ir.ui.view">
<field name="name">stock.location.path.form</field>
<field name="model">stock.location.path</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Location Paths">
<field name="name"/>
<newline/>
<field name="product_id"/>
<newline/>
<field name="location_from_id"/>
<field name="location_dest_id"/>
<field name="auto"/>
<field name="delay"/>
</form>
</field>
</record>
<record id="product_normal_form_inherit_location" model="ir.ui.view">
<field name="name">product.product.form</field>
<field name="model">product.product</field>
<field name="type">form</field>
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="arch" type="xml">
<page string="Procurement &amp; Locations">
<field name="path_ids" editable="bottom" colspan="4" nolabel="1">
<tree string="Location Paths" editable="bottom">
<field name="location_from_id"/>
<field name="location_dest_id"/>
<field name="auto"/>
<field name="delay"/>
<field name="name"/>
</tree>
</field>
</page>
</field>
</record>
</data>
</terp>