From c949d120fca5e2792b8291be5e51215594c7a10e Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 8 Sep 2008 01:24:39 +0200 Subject: [PATCH] Improved: Sales Management Project Manegement Stock Management Accounting bzr revid: fp@tinyerp.com-20080907232439-bod5fedw3o7w7u47 --- addons/account/account.py | 29 +-- addons/account/account_assert_test.xml | 6 +- addons/account/account_bank_statement.py | 2 +- addons/account/account_view.xml | 2 +- addons/account/data/account_minimal.xml | 6 +- addons/account/partner.py | 11 +- .../hr_timesheet_sheet/hr_timesheet_sheet.py | 9 +- .../hr_timesheet_sheet_view.xml | 39 ++- addons/mrp/mrp.py | 6 +- addons/product/product.py | 9 +- addons/product/product_view.xml | 2 +- addons/project/__init__.py | 2 + addons/project/company.py | 50 ++++ addons/project/project.py | 95 +++++-- addons/project/project_view.xml | 58 +++-- addons/project/project_wizard.xml | 3 +- addons/sale/sale.py | 75 +++--- addons/sale/sale_view.xml | 24 +- addons/sale/security/ir.model.access.csv | 6 +- addons/stock/product.py | 54 ++++ addons/stock/product_view.xml | 1 + addons/stock/stock.py | 236 ++++++++---------- addons/stock/stock_sequence.xml | 50 ++-- addons/stock/stock_view.xml | 114 ++++----- addons/stock_location/__init__.py | 31 +++ addons/stock_location/__terp__.py | 25 ++ addons/stock_location/stock.py | 77 ++++++ addons/stock_location/stock_view.xml | 58 +++++ 28 files changed, 697 insertions(+), 383 deletions(-) create mode 100644 addons/project/company.py create mode 100644 addons/stock_location/__init__.py create mode 100644 addons/stock_location/__terp__.py create mode 100644 addons/stock_location/stock.py create mode 100644 addons/stock_location/stock_view.xml diff --git a/addons/account/account.py b/addons/account/account.py index 8e5450f808e..64658550ae5 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -159,15 +159,6 @@ class account_account(osv.osv): return super(account_account,self).search(cr, uid, args, offset, limit, 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=''): mapping = { 'balance': "COALESCE(SUM(l.debit) - SUM(l.credit), 0) as balance ", @@ -180,7 +171,7 @@ class account_account(osv.osv): if ids2: query = self.pool.get('account.move.line')._query_get(cr, uid, context=context) - cr.execute(("SELECT l.account_id, " +\ + cr.execute(("SELECT l.account_id as id, " +\ ' , '.join(map(lambda x: mapping[x], field_names)) + "FROM " \ "account_move_line l " \ @@ -189,19 +180,16 @@ class account_account(osv.osv): "AND " + query + " " \ "GROUP BY l.account_id") % (acc_set, )) - for res in cr.fetchall(): - accounts[res[0]] = res[1:] + for res in cr.dictfetchall(): + accounts[res['id']] = res res = {} 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])]) for i in ids2: - for a in range(len(field_names)): - res[id][a] += accounts.get(i, (0.0,0.0,0.0))[a] -# TODO: if account.type is consolidation: compute all childs like before + -# currency conversion - + for a in field_names: + res[id][a] += accounts.get(i, {}).get(a, 0.0) return res def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}): @@ -233,6 +221,7 @@ class account_account(osv.osv): ('payable','Payable'), ('view','View'), ('consolidation','Consolidation'), + ('other','Others'), ('closed','Closed'), ], 'Internal Type', required=True,), @@ -474,8 +463,8 @@ class account_fiscalyear(osv.osv): while ds.strftime('%Y-%m-%d') - - @@ -29,4 +25,4 @@ - \ No newline at end of file + diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index d6c4e6aa3ed..e4ad52570b9 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -319,7 +319,7 @@ class account_bank_statement(osv.osv): cursor.execute('SELECT balance_end_real \ FROM account_bank_statement \ 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() balance_start = res and res[0] or 0.0 diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index af057fd36c5..a1f94a9608b 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -1411,7 +1411,7 @@ ir.actions.act_window account.config.fiscalyear form - form + tree new diff --git a/addons/account/data/account_minimal.xml b/addons/account/data/account_minimal.xml index 5c21ebf9aa2..69d79e3f228 100644 --- a/addons/account/data/account_minimal.xml +++ b/addons/account/data/account_minimal.xml @@ -49,7 +49,7 @@ your own chart of account. Petty Cash x 570000 - cash + other @@ -64,7 +64,7 @@ your own chart of account. Products Purchase x 600000 - expense + other @@ -78,7 +78,7 @@ your own chart of account. Products Sales x 701000 - income + other diff --git a/addons/account/partner.py b/addons/account/partner.py index db1179c7a54..ef68a12de48 100644 --- a/addons/account/partner.py +++ b/addons/account/partner.py @@ -55,13 +55,12 @@ class res_partner(osv.osv): 'credit': 'receivable', 'debit': 'payable' } - maps = {} - for i in range(len(field_names)): - maps[{'credit': 'receivable', 'debit': 'payable' }[field_names[i]]] = i - res = {}.fromkeys(ids, map(lambda x: 0.0, field_names)) + maps = {'receivable':'credit', 'payable':'debit' } + res = {} + for id in ids: + res[id] = {}.fromkeys(field_names, 0) for pid,type,val in cr.fetchall(): - if type in maps: - res[pid][maps[type]] = val + res[pid][maps[type]] = val return res def _credit_search(self, cr, uid, obj, name, args): diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index c513ee0c1ba..344d6c47bf7 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -667,11 +667,16 @@ class hr_timesheet_sheet_sheet_account(osv.osv): hr_timesheet_sheet_sheet_account() + class res_company(osv.osv): _inherit = 'res.company' _columns = { - 'timesheet_range': fields.selection([('day','Day'),('week','Week'),('month','Month'),('year','Year')], 'Timeshet range'), - '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."), + 'timesheet_range': fields.selection( + [('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 = { 'timesheet_range': lambda *args: 'month', diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml index d3d23e6fb92..5e9c83a4b6c 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml @@ -150,16 +150,16 @@ + + + + + + + + + + --> @@ -238,8 +238,8 @@ + Company inheritancy + --> res.company.sheet @@ -247,18 +247,18 @@ form - - + + - + + hr.analytic.timesheet inheritancy + --> hr.analytic.timesheet.form @@ -271,10 +271,9 @@ - + hr.attendance inheritancy + --> hr.attendance.form diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index 219253c8ccf..e9877ce1349 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -1124,11 +1124,11 @@ class StockPicking(osv.osv): # # Explode picking by replacing phantom BoMs # - def action_explode(self, cr, uid, ids, *args): - for pick in self.browse(cr, uid, ids): + def action_explode(self, cr, uid, picks, *args): + for pick in picks: for move in pick.move_lines: self.pool.get('stock.move')._action_explode(cr, uid, move) - return True + return picks StockPicking() diff --git a/addons/product/product.py b/addons/product/product.py index 2d74c3a7139..a48e3932570 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -260,8 +260,8 @@ class product_template(osv.osv): '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."), '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_po_id': fields.many2one('product.uom', 'Purchase 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), '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', 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 _product_available(self, cr, uid, ids, name, arg, context={}): - res={} - for id in ids: - res.setdefault(id, 0.0) - return res + return {}.fromkeys(ids, 0.0) return _product_available _product_qty_available = _get_product_available_func(('done',), ('in', 'out')) diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index 4ce0bf91608..c941bb0f34c 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -170,7 +170,7 @@ product.category.list product.category tree - 1 + 1 diff --git a/addons/project/__init__.py b/addons/project/__init__.py index 0f75cd3dd76..4290a3ffef6 100644 --- a/addons/project/__init__.py +++ b/addons/project/__init__.py @@ -26,7 +26,9 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ############################################################################### + import project +import company import report import wizard diff --git a/addons/project/company.py b/addons/project/company.py new file mode 100644 index 00000000000..990586c48ee --- /dev/null +++ b/addons/project/company.py @@ -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() + diff --git a/addons/project/project.py b/addons/project/project.py index 65e827685df..3b3211f56c1 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -28,13 +28,12 @@ # ############################################################################## +from lxml import etree from mx import DateTime from mx.DateTime import now import time -import netsvc from osv import fields, osv -import ir class project(osv.osv): _name = "project.project" @@ -65,7 +64,7 @@ class project(osv.osv): ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)]) res_sum = {} 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 \ WHERE project_id IN (' + ','.join([str(x) for x in ids2]) + ') \ AND active \ @@ -97,7 +96,7 @@ class project(osv.osv): if not ids: return res cr.execute('''SELECT - project_id, sum(progress*planned_hours), sum(planned_hours) + project_id, sum(progress*total_hours), sum(total_hours) FROM project_task WHERE @@ -132,7 +131,7 @@ class project(osv.osv): 'warn_header': fields.text('Mail header'), 'warn_footer': fields.text('Mail footer'), '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), } @@ -203,16 +202,30 @@ class task(osv.osv): t3 += map(lambda x: (x,t2[1]+1), t2[0].child_ids) 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)) 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 = {} - for id in ids: - res[id] = 0.0 - for task_id, sum in cr.fetchall(): - res[task_id] = sum + for task in self.browse(cr, uid, ids, context=context): + res[task.id] = {} + res[task.id]['effective_hours'] = hours.get(task.id, 0.0) + 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 + 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 = { 'active': fields.boolean('Active'), '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'), 'sequence': fields.integer('Sequence'), '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_deadline': fields.datetime('Deadline'), '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"), 'notes': fields.text('Notes'), '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'), - 'progress': fields.integer('Progress (0-100)'), + + 'planned_hours': fields.float('Planned Hours', readonly=True, states={'draft':[('readonly',False)]}), + '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'), '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 = { 'user_id': lambda obj,cr,uid,context: uid, - 'state': lambda *a: 'open', + 'state': lambda *a: 'draft', 'priority': lambda *a: '2', 'progress': lambda *a: 0, 'sequence': lambda *a: 10, @@ -249,6 +267,31 @@ class task(osv.osv): } _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): request = self.pool.get('res.request') tasks = self.browse(cr, uid, ids) @@ -265,7 +308,7 @@ class task(osv.osv): 'ref_doc1': 'project.task,%d'% (task.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'): self.do_reopen(cr, uid, [task.parent_id.id]) return True @@ -304,7 +347,7 @@ class task(osv.osv): 'ref_doc1': 'project.task,%d' % task.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 def do_open(self, cr, uid, ids, *args): @@ -331,7 +374,7 @@ class project_work(osv.osv): _columns = { 'name': fields.char('Work summary', size=128), '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'), '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') } _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() diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 4bcc2efe358..9f59af14e04 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -26,12 +26,12 @@