diff --git a/addons/account/account.py b/addons/account/account.py index 95c9b9f7b71..e13858c73ce 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -243,7 +243,8 @@ class account_account(osv.osv): 'balance': "COALESCE(SUM(l.debit),0) " \ "- COALESCE(SUM(l.credit), 0) as balance", 'debit': "COALESCE(SUM(l.debit), 0) as debit", - 'credit': "COALESCE(SUM(l.credit), 0) as credit" + 'credit': "COALESCE(SUM(l.credit), 0) as credit", + 'foreign_balance': "COALESCE(SUM(l.amount_currency), 0) as foreign_balance", } #get all the necessary accounts children_and_consolidated = self._get_children_and_consol(cr, uid, ids, context=context) @@ -270,7 +271,7 @@ class account_account(osv.osv): # ON l.account_id = tmp.id # or make _get_children_and_consol return a query and join on that request = ("SELECT l.account_id as id, " +\ - ', '.join(map(mapping.__getitem__, field_names)) + + ', '.join(map(mapping.__getitem__, mapping.keys())) + " FROM account_move_line l" \ " WHERE l.account_id IN %s " \ + filters + @@ -289,7 +290,7 @@ class account_account(osv.osv): sums = {} currency_obj = self.pool.get('res.currency') while brs: - current = brs[0] + current = brs.pop(0) # can_compute = True # for child in current.child_id: # if child.id not in sums: @@ -299,7 +300,6 @@ class account_account(osv.osv): # except ValueError: # brs.insert(0, child) # if can_compute: - brs.pop(0) for fn in field_names: sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0) for child in current.child_id: @@ -307,6 +307,16 @@ class account_account(osv.osv): sums[current.id][fn] += sums[child.id][fn] else: sums[current.id][fn] += currency_obj.compute(cr, uid, child.company_id.currency_id.id, current.company_id.currency_id.id, sums[child.id][fn], context=context) + + # as we have to relay on values computed before this is calculated separately than previous fields + if current.currency_id and current.exchange_rate and \ + ('adjusted_balance' in field_names or 'unrealized_gain_loss' in field_names): + # Computing Adjusted Balance and Unrealized Gains and losses + # Adjusted Balance = Foreign Balance / Exchange Rate + # Unrealized Gains and losses = Adjusted Balance - Balance + adj_bal = sums[current.id].get('foreign_balance', 0.0) / current.exchange_rate + sums[current.id].update({'adjusted_balance': adj_bal, 'unrealized_gain_loss': adj_bal - sums[current.id].get('balance', 0.0)}) + for id in ids: res[id] = sums.get(id, null_result) else: @@ -340,9 +350,10 @@ class account_account(osv.osv): accounts = self.browse(cr, uid, ids, context=context) for account in accounts: level = 0 - if account.parent_id: - obj = self.browse(cr, uid, account.parent_id.id) - level = obj.level + 1 + parent = account.parent_id + while parent: + level += 1 + parent = parent.parent_id res[account.id] = level return res @@ -367,8 +378,8 @@ class account_account(osv.osv): move_obj = self.pool.get('account.move.line') move_id = move_obj.search(cr, uid, [ - ('journal_id','=',jids[0]), - ('period_id','=',pids[0]), + ('journal_id','=',jids[0]), + ('period_id','=',pids[0]), ('account_id','=', account_id), (name,'>', 0.0), ('name','=', _('Opening Balance')) @@ -418,7 +429,14 @@ class account_account(osv.osv): 'balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Balance', multi='balance'), 'credit': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Credit', multi='balance'), 'debit': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Debit', multi='balance'), + 'foreign_balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Foreign Balance', multi='balance', + help="Total amount (in Secondary currency) for transactions held in secondary currency for this account."), + 'adjusted_balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Adjusted Balance', multi='balance', + help="Total amount (in Company currency) for transactions held in secondary currency for this account."), + 'unrealized_gain_loss': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Unrealized Gain or Loss', multi='balance', + help="Value of Loss or Gain due to changes in exchange rate when doing multi-currency transactions."), 'reconcile': fields.boolean('Allow Reconciliation', help="Check this box if this account allows reconciliation of journal items."), + 'exchange_rate': fields.related('currency_id', 'rate', type='float', string='Exchange Rate', digits=(12,6)), 'shortcut': fields.char('Shortcut', size=12), 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel', 'account_id', 'tax_id', 'Default Taxes'), @@ -436,7 +454,10 @@ class account_account(osv.osv): 'manage this. So if you import from another software system you may have to use the rate at date. ' \ 'Incoming transactions always use the rate at date.', \ required=True), - 'level': fields.function(_get_level, string='Level', store=True, type='integer'), + 'level': fields.function(_get_level, string='Level', method=True, type='integer', + store={ + 'account.account': (_get_children_and_consol, ['level', 'parent_id'], 10), + }), } _defaults = { @@ -884,9 +905,16 @@ class account_fiscalyear(osv.osv): return True def find(self, cr, uid, dt=None, exception=True, context=None): + if context is None: context = {} if not dt: dt = time.strftime('%Y-%m-%d') - ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)]) + args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)] + if context.get('company_id', False): + args.append(('company_id', '=', context['company_id'])) + else: + company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id + args.append(('company_id', '=', company_id)) + ids = self.search(cr, uid, args, context=context) if not ids: if exception: raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.')) @@ -930,7 +958,7 @@ class account_period(osv.osv): _sql_constraints = [ ('name_company_uniq', 'unique(name, company_id)', 'The name of the period must be unique per company!'), ] - + def _check_duration(self,cr,uid,ids,context=None): obj_period = self.browse(cr, uid, ids[0], context=context) if obj_period.date_stop < obj_period.date_start: diff --git a/addons/account/account_menuitem.xml b/addons/account/account_menuitem.xml index 89538603ae7..6c651405c92 100644 --- a/addons/account/account_menuitem.xml +++ b/addons/account/account_menuitem.xml @@ -43,6 +43,7 @@ + [('parent_id','=',False)] + + Unrealized Gain or Loss + account.account + tree + + + + + + + + + + + + + + + + Unrealized Gain or Loss + account.account + form + tree + + [('currency_id','!=',False)] + When doing multi-currency transactions, you may loose or gain some amount due to changes of exchange rate. This menu gives you a forecast of the Gain or Loss you'd realized if those transactions were ended today. Only for accounts having a secondary currency set. + + + + - + Payroll 2 - + diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 3310dbcf4ef..6b863121369 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -286,24 +286,21 @@ kanban + + + +
- - - - - -
- - - - - -
+
diff --git a/addons/mrp_repair/mrp_repair.py b/addons/mrp_repair/mrp_repair.py index 38cb6bf2252..e1216332ae4 100644 --- a/addons/mrp_repair/mrp_repair.py +++ b/addons/mrp_repair/mrp_repair.py @@ -665,7 +665,7 @@ class mrp_repair_line(osv.osv, ProductChangeMixin): 'product_uom_qty': lambda *a: 1, } - def onchange_operation_type(self, cr, uid, ids, type, guarantee_limit): + def onchange_operation_type(self, cr, uid, ids, type, guarantee_limit, company_id=False, context=None): """ On change of operation type it sets source location, destination location and to invoice field. @param product: Changed operation type. @@ -676,27 +676,32 @@ class mrp_repair_line(osv.osv, ProductChangeMixin): return {'value': { 'location_id': False, 'location_dest_id': False - } - } + }} + warehouse_obj = self.pool.get('stock.warehouse') + location_id = self.pool.get('stock.location').search(cr, uid, [('usage','=','production')], context=context) + location_id = location_id and location_id[0] or False + + if type == 'add': + # TOCHECK: Find stock location for user's company warehouse or + # repair order's company's warehouse (company_id field is added in fix of lp:831583) + args = company_id and [('company_id', '=', company_id)] or [] + warehouse_ids = warehouse_obj.search(cr, uid, args, context=context) + stock_id = False + if warehouse_ids: + stock_id = warehouse_obj.browse(cr, uid, warehouse_ids[0], context=context).lot_stock_id.id + to_invoice = (guarantee_limit and datetime.strptime(guarantee_limit, '%Y-%m-%d') < datetime.now()) - product_id = self.pool.get('stock.location').search(cr, uid, [('name','=','Production')])[0] - if type != 'add': return {'value': { - 'to_invoice': False, - 'location_id': product_id, - 'location_dest_id': False - } - } + 'to_invoice': to_invoice, + 'location_id': stock_id, + 'location_dest_id': location_id + }} - stock_id = self.pool.get('stock.location').search(cr, uid, [('name','=','Stock')])[0] - to_invoice = (guarantee_limit and - datetime.strptime(guarantee_limit, '%Y-%m-%d') < datetime.now()) return {'value': { - 'to_invoice': to_invoice, - 'location_id': stock_id, - 'location_dest_id': product_id - } - } + 'to_invoice': False, + 'location_id': location_id, + 'location_dest_id': False + }} mrp_repair_line() diff --git a/addons/mrp_repair/mrp_repair_view.xml b/addons/mrp_repair/mrp_repair_view.xml index fc36e643a3e..098bc638110 100644 --- a/addons/mrp_repair/mrp_repair_view.xml +++ b/addons/mrp_repair/mrp_repair_view.xml @@ -59,7 +59,7 @@ - + @@ -77,7 +77,7 @@ - + diff --git a/addons/mrp_repair/test/test_mrp_repair.yml b/addons/mrp_repair/test/test_mrp_repair.yml index e8010cc1bdc..27cc94da3a8 100644 --- a/addons/mrp_repair/test/test_mrp_repair.yml +++ b/addons/mrp_repair/test/test_mrp_repair.yml @@ -61,7 +61,7 @@ On change of operation type it sets source location, destination location and to invoice field - !python {model: mrp.repair.line}: | - self.onchange_operation_type(cr, uid, [ref('mrp_repair_rma0')], type, False) + self.onchange_operation_type(cr, uid, [ref('mrp_repair_rma0')], type, False, ref('base.main_company'), {}) - On change of partner sets the values of partner address, partner invoice address and pricelist. - diff --git a/addons/portal/wizard/share_wizard.py b/addons/portal/wizard/share_wizard.py index c1366b5dbf0..ab639bdeccc 100644 --- a/addons/portal/wizard/share_wizard.py +++ b/addons/portal/wizard/share_wizard.py @@ -40,10 +40,6 @@ class share_wizard_portal(osv.osv_memory): _columns = { 'user_ids': fields.many2many('res.users', 'share_wizard_res_user_rel', 'share_id', 'user_id', 'Existing users', domain=[('share', '=', True)]), 'group_ids': fields.many2many('res.groups', 'share_wizard_res_group_rel', 'share_id', 'group_id', 'Existing groups', domain=[('share', '=', False)]), - - # no clean way to extend selection yet, copy the field. - 'user_type': fields.selection(_user_type_selection,'Users to share with', - help="Select the type of user(s) you would like to share data with."), } def is_portal_manager(self, cr, uid, context=None): diff --git a/addons/portal/wizard/share_wizard_view.xml b/addons/portal/wizard/share_wizard_view.xml index f16334b68a3..9bee027c3e2 100644 --- a/addons/portal/wizard/share_wizard_view.xml +++ b/addons/portal/wizard/share_wizard_view.xml @@ -8,11 +8,7 @@ form - - - - - + diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index 254a75fdcdc..7600eb93c87 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -202,39 +202,33 @@ kanban + + + +
-
- - - - -
- [] - - - -
+
+ +
- @@ -255,7 +249,7 @@ - + Products ir.actions.act_window diff --git a/addons/project/project.py b/addons/project/project.py index 05bed8a9b9a..a5cd8928dfc 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -471,6 +471,7 @@ class task(osv.osv): 'company_id': fields.many2one('res.company', 'Company'), 'id': fields.integer('ID', readonly=True), 'color': fields.integer('Color Index'), + 'user_email': fields.related('user_id', 'user_email', type='char', string='User Email', readonly=True), } _defaults = { @@ -484,7 +485,7 @@ class task(osv.osv): 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'project.task', context=c) } - _order = "sequence,priority, date_start, name, id" + _order = "priority, sequence, date_start, name, id" def set_priority(self, cr, uid, ids, priority): """Set task priority diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 94724993a9d..18ff634f796 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -175,7 +175,7 @@ {'search_default_Current':1} A project contains a set of tasks or issues that will be performed by your resources assigned to it. A project can be hierarchically structured, as a child of a Parent Project. This allows you to design large project structures with different phases spread over the project duration cycle. Each user can set his default project in his own preferences to automatically filter the tasks or issues he usually works on. If you choose to invoice the time spent on a project task, you can find project tasks to be invoiced in the billing section. - + Templates of Projects @@ -323,12 +323,14 @@ - + + + - -
+ http://pad.openerp.com/ +
- - - - - - + +
No Stock
On Hand , Available
-
Public Price : , Cost :
+
+ Public Price : , + Cost : +
@@ -338,40 +340,31 @@
- +
-
- +
+ +
+ - + +
-
- -
-
-
- +
+
+
@@ -467,8 +460,8 @@ - - + + tree,form,calendar,gantt,graph,kanban - {"search_default_user_id":uid, "search_default_current": 1} + {"search_default_user_id":uid, "search_default_draft": 1, "search_default_open":1 } A task represents a work that has to be done. Each user works in his own list of tasks where he can record his task work in hours. He can work and close the task itself or delegate it to another user. If you delegate a task to another user, you get a new task in pending state, which will be reopened when you have to review the work achieved. If you install the project_timesheet module, task work can be invoiced based on the project configuration. With the project_mrp module, sales orders can create tasks automatically when they are confirmed. diff --git a/addons/project/static/src/img/kanban-pad-openerp.png b/addons/project/static/src/img/kanban-pad-openerp.png new file mode 100644 index 00000000000..ad64c5aea0e Binary files /dev/null and b/addons/project/static/src/img/kanban-pad-openerp.png differ diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py index e94349e028f..5575eee71d8 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -226,7 +226,8 @@ class project_issue(crm.crm_case, osv.osv): multi='compute_day', type="float", store=True), 'inactivity_days': fields.function(_compute_day, string='Days since last action', \ multi='compute_day', type="integer", help="Difference in days between last action and current date"), - 'color': fields.integer('Color Index'), + 'color': fields.integer('Color Index'), + 'user_email': fields.related('user_id', 'user_email', type='char', string='User Email', readonly=True), 'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]), 'date_action_last': fields.datetime('Last Action', readonly=1), 'date_action_next': fields.datetime('Next Action', readonly=1), diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index 00e6a00c163..c6d852cfde0 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -261,15 +261,19 @@ - + - + Project Issue Kanban project.issue kanban - + + + + +
diff --git a/addons/project_long_term/__openerp__.py b/addons/project_long_term/__openerp__.py index ef57f53e5f1..31522d63ea2 100644 --- a/addons/project_long_term/__openerp__.py +++ b/addons/project_long_term/__openerp__.py @@ -49,13 +49,11 @@ Features 'test/test_schedule_phases_case2.yml', 'test/project_schedule_consecutive_day.yml', 'test/project_schedule_without_wroking_hour.yml', - 'test/schedule_phase_tasks.yml', 'test/phase_constraint.yml', 'test/test_schedule_tasks_case1.yml', ], "update_xml": [ "security/ir.model.access.csv", - "wizard/project_schedule_tasks_view.xml", "project_long_term_view.xml", "project_long_term_workflow.xml", "wizard/project_compute_phases_view.xml", diff --git a/addons/project_long_term/project_long_term.py b/addons/project_long_term/project_long_term.py index 7cd6697b67a..8588b90f5a2 100644 --- a/addons/project_long_term/project_long_term.py +++ b/addons/project_long_term/project_long_term.py @@ -22,7 +22,7 @@ from datetime import datetime from tools.translate import _ from osv import fields, osv -from resource.faces import task as Task +from resource.faces import task as Task from operator import itemgetter class project_phase(osv.osv): @@ -74,40 +74,43 @@ class project_phase(osv.osv): return False return True - def _check_constraint_start(self, cr, uid, ids, context=None): - phase = self.read(cr, uid, ids[0], ['date_start', 'constraint_date_start'], context=context) - if phase['date_start'] and phase['constraint_date_start'] and phase['date_start'] < phase['constraint_date_start']: - return False - return True - - def _check_constraint_end(self, cr, uid, ids, context=None): - phase = self.read(cr, uid, ids[0], ['date_end', 'constraint_date_end'], context=context) - if phase['date_end'] and phase['constraint_date_end'] and phase['date_end'] > phase['constraint_date_end']: - return False - return True - def _get_default_uom_id(self, cr, uid): model_data_obj = self.pool.get('ir.model.data') model_data_id = model_data_obj._get_id(cr, uid, 'product', 'uom_hour') return model_data_obj.read(cr, uid, [model_data_id], ['res_id'])[0]['res_id'] - def _compute(self, cr, uid, ids, field_name, arg, context=None): + def _compute_progress(self, cr, uid, ids, field_name, arg, context=None): res = {} if not ids: return res for phase in self.browse(cr, uid, ids, context=context): - tot = 0.0 + if phase.state=='done': + res[phase.id] = 100.0 + continue + elif phase.state=="cancelled": + res[phase.id] = 0.0 + continue + elif not phase.task_ids: + res[phase.id] = 0.0 + continue + + tot = done = 0.0 for task in phase.task_ids: - tot += task.planned_hours - res[phase.id] = tot + tot += task.total_hours + done += min(task.effective_hours, task.total_hours) + + if not tot: + res[phase.id] = 0.0 + else: + res[phase.id] = round(100.0 * done / tot, 2) return res _columns = { 'name': fields.char("Name", size=64, required=True), - 'date_start': fields.date('Start Date', help="It's computed by the scheduler according the project date or the end date of the previous phase.", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), - 'date_end': fields.date('End Date', help=" It's computed by the scheduler according to the start date and the duration.", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), - 'constraint_date_start': fields.date('Minimum Start Date', help='force the phase to start after this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), - 'constraint_date_end': fields.date('Deadline', help='force the phase to finish before this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), + 'date_start': fields.datetime('Start Date', help="It's computed by the scheduler according the project date or the end date of the previous phase.", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), + 'date_end': fields.datetime('End Date', help=" It's computed by the scheduler according to the start date and the duration.", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), + 'constraint_date_start': fields.datetime('Minimum Start Date', help='force the phase to start after this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), + 'constraint_date_end': fields.datetime('Deadline', help='force the phase to finish before this date', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), 'project_id': fields.many2one('project.project', 'Project', required=True), 'next_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'prv_phase_id', 'next_phase_id', 'Next Phases', states={'cancelled':[('readonly',True)]}), 'previous_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'next_phase_id', 'prv_phase_id', 'Previous Phases', states={'cancelled':[('readonly',True)]}), @@ -115,74 +118,27 @@ class project_phase(osv.osv): 'duration': fields.float('Duration', required=True, help="By default in days", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), 'product_uom': fields.many2one('product.uom', 'Duration UoM', required=True, help="UoM (Unit of Measure) is the unit of measurement for Duration", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), 'task_ids': fields.one2many('project.task', 'phase_id', "Project Tasks", states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), - 'resource_ids': fields.one2many('project.resource.allocation', 'phase_id', "Project Resources",states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), - 'responsible_id': fields.many2one('res.users', 'Responsible', states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}), + 'user_force_ids': fields.many2many('res.users', string='Force Assigned Users'), + 'user_ids': fields.one2many('project.user.allocation', 'phase_id', "Assigned Users",states={'done':[('readonly',True)], 'cancelled':[('readonly',True)]}, + help="The ressources on the project can be computed automatically by the scheduler"), 'state': fields.selection([('draft', 'New'), ('open', 'In Progress'), ('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State', readonly=True, required=True, help='If the phase is created the state \'Draft\'.\n If the phase is started, the state becomes \'In Progress\'.\n If review is needed the phase is in \'Pending\' state.\ \n If the phase is over, the states is set to \'Done\'.'), - 'total_hours': fields.function(_compute, string='Total Hours'), + 'progress': fields.function(_compute_progress, string='Progress', help="Computed based on related tasks"), } _defaults = { - 'responsible_id': lambda obj,cr,uid,context: uid, 'state': 'draft', 'sequence': 10, 'product_uom': lambda self,cr,uid,c: self.pool.get('product.uom').search(cr, uid, [('name', '=', _('Day'))], context=c)[0] } - _order = "project_id, date_start, sequence, name" + _order = "project_id, date_start, sequence" _constraints = [ (_check_recursion,'Loops in phases not allowed',['next_phase_ids', 'previous_phase_ids']), (_check_dates, 'Phase start-date must be lower than phase end-date.', ['date_start', 'date_end']), ] def onchange_project(self, cr, uid, ids, project, context=None): - result = {} - result['date_start'] = False - project_obj = self.pool.get('project.project') - if project: - project_id = project_obj.browse(cr, uid, project, context=context) - result['date_start'] = project_id.date_start - return {'value': result} - - - def _check_date_start(self, cr, uid, phase, date_end, context=None): - """ - Check And Compute date_end of phase if change in date_start < older time. - """ - uom_obj = self.pool.get('product.uom') - resource_obj = self.pool.get('resource.resource') - cal_obj = self.pool.get('resource.calendar') - calendar_id = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False - resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)]) - if resource_id: - res = resource_obj.read(cr, uid, resource_id, ['calendar_id'], context=context)[0] - cal_id = res.get('calendar_id', False) and res.get('calendar_id')[0] or False - if cal_id: - calendar_id = cal_id - default_uom_id = self._get_default_uom_id(cr, uid) - avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id) - work_times = cal_obj.interval_min_get(cr, uid, calendar_id, date_end, avg_hours or 0.0, resource_id and resource_id[0] or False) - dt_start = work_times[0][0].strftime('%Y-%m-%d') - self.write(cr, uid, [phase.id], {'date_start': dt_start, 'date_end': date_end.strftime('%Y-%m-%d')}, context=context) - - def _check_date_end(self, cr, uid, phase, date_start, context=None): - """ - Check And Compute date_end of phase if change in date_end > older time. - """ - uom_obj = self.pool.get('product.uom') - resource_obj = self.pool.get('resource.resource') - cal_obj = self.pool.get('resource.calendar') - calendar_id = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False - resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)], context=context) - if resource_id: - res = resource_obj.read(cr, uid, resource_id, ['calendar_id'], context=context)[0] - cal_id = res.get('calendar_id', False) and res.get('calendar_id')[0] or False - if cal_id: - calendar_id = cal_id - default_uom_id = self._get_default_uom_id(cr, uid) - avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id) - work_times = cal_obj.interval_get(cr, uid, calendar_id, date_start, avg_hours or 0.0, resource_id and resource_id[0] or False) - dt_end = work_times[-1][1].strftime('%Y-%m-%d') - self.write(cr, uid, [phase.id], {'date_start': date_start.strftime('%Y-%m-%d'), 'date_end': dt_end}, context=context) + return {} def copy(self, cr, uid, id, default=None, context=None): if default is None: @@ -211,208 +167,57 @@ class project_phase(osv.osv): self.write(cr, uid, ids, {'state': 'done'}) return True - def generate_phase(self, cr, uid, ids, f, parent=False, context=None): - if context is None: - context = {} - phase_ids = [] - data_pool = self.pool.get('ir.model.data') - uom_pool = self.pool.get('product.uom') - task_pool = self.pool.get('project.task') - data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day') - for phase in self.browse(cr, uid, ids, context=context): - avg_days = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, day_uom_id) - duration = str(avg_days) + 'd' - # Create a new project for each phase - str_resource = ('%s & '*len(phase.resource_ids))[:-2] - str_vals = str_resource % tuple(map(lambda x: 'Resource_%s'%x.resource_id.id, phase.resource_ids)) + def generate_phase(self, cr, uid, phases, context=None): + context = context or {} + result = "" - # Phases Defination for the Project - s = ''' + task_pool = self.pool.get('project.task') + for phase in phases: + if phase.state in ('done','cancelled'): + continue + duration_uom = { + 'days': 'd', 'day': 'd', 'd':'d', + 'months': 'm', 'month':'month', 'm':'m', + 'weeks': 'w', 'week': 'w', 'w':'w', + 'hours': 'H', 'hour': 'H', 'h':'H', + }.get(phase.product_uom.name.lower(), "h") + duration = str(phase.duration) + duration_uom + result += ''' def Phase_%s(): - title = \"%s\" - effort = \'%s\' - resource = %s -'''%(phase.id, phase.name, duration, str_vals or False) - - # Recalculate date_start and date_end - # according to constraints on date start and date end on phase - start_date = '' - end_date = '' + effort = \"%s\"''' % (phase.id, duration) + start = [] if phase.constraint_date_start: - start_date = datetime.strptime(phase.constraint_date_start, '%Y-%m-%d') - s += ''' - start = \"%s\" -'''%(datetime.strftime(start_date, "%Y-%m-%d")) - else: - if parent: - start = 'up.Phase_%s.end' % (parent.id) - s += ''' - start = %s -'''%(start) - else: - start = phase.project_id.date_start or phase.date_start - s += ''' - start = \"%s\" -'''%(start) - - if phase.constraint_date_end : - end_date= datetime.strptime(phase.constraint_date_end, '%Y-%m-%d') - s += ''' - end = \"%s\" -'''%(datetime.strftime(end_date, "%Y-%m-%d")) + start.append('datetime.datetime.strptime("'+str(phase.constraint_date_start)+'", "%Y-%m-%d %H:%M:%S")') + for previous_phase in phase.previous_phase_ids: + start.append("up.Phase_%s.end" % (previous_phase.id,)) + if start: + result += ''' + start = max(%s) +''' % (','.join(start)) + if phase.user_force_ids: + result += ''' + resource = %s +''' % '|'.join(map(lambda x: 'User_'+str(x.id), phase.user_force_ids)) - #start = datetime.strftime((datetime.strptime(start, "%Y-%m-%d")), "%Y-%m-%d") + result += task_pool._generate_task(cr, uid, phase.task_ids, ident=8, context=context) + result += "\n" - phase_ids.append(phase.id) - parent = False - task_ids = [] - todo_task_ids = task_pool.search(cr, uid, [('id', 'in', map(lambda x : x.id, phase.task_ids)), - ('state', 'in', ['draft', 'open', 'pending']) - ], order='sequence') - if todo_task_ids : - for task in task_pool.browse(cr, uid, todo_task_ids, context=context): - s += task_pool.generate_task(cr, uid, task.id, parent=parent, flag=False, context=context) - if not parent: - parent = task - task_ids.append(task.id) - - f += s + '\n' - # Recursive call till all the next phases scheduled - for next_phase in phase.next_phase_ids: - if next_phase.state in ['draft', 'open', 'pending']: - rf, rphase_ids = self.generate_phase(cr, uid, [next_phase.id], f = '', parent=phase, context=context) - f += rf +'\n' - phase_ids += rphase_ids - else: - continue - return f, phase_ids - - def schedule_tasks(self, cr, uid, ids, context=None): - """ - Schedule tasks base on faces lib - """ - if context is None: - context = {} - if type(ids) in (long, int,): - ids = [ids] - task_pool = self.pool.get('project.task') - resource_pool = self.pool.get('resource.resource') - for phase in self.browse(cr, uid, ids, context=context): - project = phase.project_id - calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False - start_date = project.date_start - #Creating resources using the member of the Project - u_ids = [i.id for i in project.members] - resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context) - start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d") - func_str = '' - start = start_date - minimum_time_unit = 1 - # default values - working_hours_per_day = 24 - working_days_per_week = 7 - working_days_per_month = 30 - working_days_per_year = 365 - - vacation = [] - if calendar_id: - working_hours_per_day = 8 #TODO: it should be come from calendars - working_days_per_week = 5 - working_days_per_month = 20 - working_days_per_year = 200 - vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context)) - - working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context) - - cls_str = '' - # Creating Resources for the Project - for key, vals in resource_objs.items(): - cls_str +=''' - class Resource_%s(Resource): - title = \"%s\" - vacation = %s - efficiency = %s -'''%(key, vals.get('name',False), vals.get('vacation', False), vals.get('efficiency', False)) - - # Create a new project for each phase - func_str += ''' -def Phase_%d(): - from resource.faces import Resource - title = \"%s\" - start = \'%s\' - minimum_time_unit = %s - working_hours_per_day = %s - working_days_per_week = %s - working_days_per_month = %s - working_days_per_year = %s - vacation = %s - working_days = %s -'''%(phase.id, phase.name, start, minimum_time_unit, working_hours_per_day, working_days_per_week, working_days_per_month, working_days_per_year, vacation, working_days ) - - parent = False - task_ids = [] - todo_task_ids = task_pool.search(cr, uid, [('id', 'in', map(lambda x : x.id, phase.task_ids)), - ('state', 'in', ['draft', 'open', 'pending']) - ], order='sequence') - for task in task_pool.browse(cr, uid, todo_task_ids, context=context): - func_str += task_pool.generate_task(cr, uid, task.id, parent=parent, flag=True, context=context) - if not parent: - parent = task - task_ids.append(task.id) - func_str += cls_str - phase_id = phase.id - #check known constraints before running Face algorithm in order to have the error translated - if not phase.project_id.date_start: - raise osv.except_osv(_('Error !'),_('Task Scheduling is not possible.\nProject should have the Start date for scheduling.')) - # Allocating Memory for the required Project and Pahses and Resources - exec(func_str) - Phase = eval('Phase_%d' % phase.id) - phase = None - try: - phase = Task.BalancedProject(Phase) - except Exception, e: - raise osv.except_osv(_('Error !'),e) - - for task_id in task_ids: - task = eval("phase.Task_%d" % task_id) - start_date = task.start.to_datetime() - end_date = task.end.to_datetime() - - task_pool.write(cr, uid, [task_id], { - 'date_start': start_date.strftime('%Y-%m-%d %H:%M:%S'), - 'date_end': end_date.strftime('%Y-%m-%d %H:%M:%S') - }, context=context) - return True + return result project_phase() -class project_resource_allocation(osv.osv): - _name = 'project.resource.allocation' - _description = 'Project Resource Allocation' - _rec_name = 'resource_id' - - def get_name(self, cr, uid, ids, field_name, arg, context=None): - res = {} - for allocation in self.browse(cr, uid, ids, context=context): - name = allocation.phase_id.name - name += ' (%s%%)' %(allocation.useability) - res[allocation.id] = name - return res +class project_user_allocation(osv.osv): + _name = 'project.user.allocation' + _description = 'Phase User Allocation' + _rec_name = 'user_id' _columns = { - 'name': fields.function(get_name, type='char', size=256), - 'resource_id': fields.many2one('resource.resource', 'Resource', required=True), + 'user_id': fields.many2one('res.users', 'User', required=True), 'phase_id': fields.many2one('project.phase', 'Project Phase', ondelete='cascade', required=True), 'project_id': fields.related('phase_id', 'project_id', type='many2one', relation="project.project", string='Project', store=True), - 'user_id': fields.related('resource_id', 'user_id', type='many2one', relation="res.users", string='User'), - 'date_start': fields.date('Start Date', help="Starting Date"), - 'date_end': fields.date('End Date', help="Ending Date"), - 'useability': fields.float('Availability', help="Availability of this resource for this project phase in percentage (=50%)"), + 'date_start': fields.datetime('Start Date', help="Starting Date"), + 'date_end': fields.datetime('End Date', help="Ending Date"), } - _defaults = { - 'useability': 100, - } - -project_resource_allocation() +project_user_allocation() class project(osv.osv): _inherit = "project.project" @@ -420,161 +225,102 @@ class project(osv.osv): 'phase_ids': fields.one2many('project.phase', 'project_id', "Project Phases"), 'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ), } - def generate_members(self, cr, uid, ids, context=None): - """ - Return a list of Resource Class objects for the resources allocated to the phase. - """ - res = {} - resource_pool = self.pool.get('resource.resource') - for project in self.browse(cr, uid, ids, context=context): - user_ids = map(lambda x:x.id, project.members) - calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False - resource_objs = resource_pool.generate_resources(cr, uid, user_ids, calendar_id, context=context) - res[project.id] = resource_objs - return res - - def schedule_phases(self, cr, uid, ids, context=None): - """ - Schedule phase base on faces lib - """ - if context is None: - context = {} + def _schedule_header(self, cr, uid, ids, context=None): + context = context or {} if type(ids) in (long, int,): ids = [ids] - phase_pool = self.pool.get('project.phase') - task_pool = self.pool.get('project.task') + projects = self.browse(cr, uid, ids, context=context) + + for project in projects: + if not project.members: + raise osv.except_osv(_('Warning !'),_("You must assign members on the project '%s' !") % (project.name,)) + resource_pool = self.pool.get('resource.resource') - data_pool = self.pool.get('ir.model.data') - resource_allocation_pool = self.pool.get('project.resource.allocation') - data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day') - - #Checking the Valid Phase resource allocation from project member - for project in self.browse(cr, uid, ids, context=context): - flag = False - res_missing = [] - members_ids = [] - if project.members: - members_ids = [user.id for user in project.members] - for phase in project.phase_ids: - if phase.resource_ids: - res_ids = [ re.id for re in phase.resource_ids] - for res in resource_allocation_pool.browse(cr, uid, res_ids, context=context): - if res.resource_id.user_id.id not in members_ids: - res_missing += [res.resource_id.name] - flag = True - if flag: - raise osv.except_osv(_('Warning !'),_("Resource(s) %s is(are) not member(s) of the project '%s' .") % (",".join(res_missing), project.name)) + result = "from resource.faces import *\n" + result += "import datetime\n" for project in self.browse(cr, uid, ids, context=context): - root_phase_ids = phase_pool.search(cr, uid, [('project_id', '=', project.id), - ('state', 'in', ['draft', 'open', 'pending']), - ('previous_phase_ids', '=', False) - ]) - calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False - start_date = project.date_start - #if start_date: - # start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d") - #Creating resources using the member of the Project u_ids = [i.id for i in project.members] + for task in project.tasks: + if task.state in ('done','cancelled'): + continue + if task.user_id and (task.user_id.id not in u_ids): + u_ids.append(task.user_id.id) + calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context) - func_str = '' - start = start_date - minimum_time_unit = 1 - # default values - working_hours_per_day = 24 - working_days_per_week = 7 - working_days_per_month = 30 - working_days_per_year = 365 - - vacation = [] - if calendar_id: - working_hours_per_day = 8 #TODO: it should be come from calendars - working_days_per_week = 5 - working_days_per_month = 20 - working_days_per_year = 200 - vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context)) - - working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context) - - cls_str = '' - # Creating Resources for the Project for key, vals in resource_objs.items(): - cls_str +=''' - class Resource_%s(Resource): - title = \"%s\" - vacation = %s - efficiency = %s -'''%(key, vals.get('name',False), vals.get('vacation', False), vals.get('efficiency', False)) - - # Create a new project for each phase - func_str += ''' -def Project_%d(): - from resource.faces import Resource - title = \"%s\" + result +=''' +class User_%s(Resource): + efficiency = %s +''' % (key, vals.get('efficiency', False)) + + result += ''' +def Project(): + ''' + return result + + def _schedule_project(self, cr, uid, project, context=None): + resource_pool = self.pool.get('resource.resource') + calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False + working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context) + # TODO: check if we need working_..., default values are ok. + result = """ + def Project_%d(): start = \'%s\' - minimum_time_unit = %s - working_hours_per_day = %s - working_days_per_week = %s - working_days_per_month = %s - working_days_per_year = %s + working_days = %s + resource = %s +""" % ( + project.id, + project.date_start, working_days, + '|'.join(['User_'+str(x.id) for x in project.members]) + ) + vacation = calendar_id and tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context)) or False + if vacation: + result+= """ vacation = %s - working_days = %s -'''%(project.id, project.name, start, minimum_time_unit, working_hours_per_day, working_days_per_week, working_days_per_month, working_days_per_year, vacation, working_days ) - - func_str += cls_str - phase_ids = [] - for root_phase in phase_pool.browse(cr, uid, root_phase_ids, context=context): - phases, child_phase_ids = phase_pool.generate_phase(cr, uid, [root_phase.id], '', context=context) - func_str += phases - phase_ids += child_phase_ids - - project_id = project.id - if not project.date_start: - raise osv.except_osv(_('Error !'),_('Task Scheduling is not possible.\nProject should have the Start date for scheduling.')) - # Allocating Memory for the required Project and Phases and Resources - exec(func_str) - Project = eval('Project_%d' % project.id) - project = None - try: - project = Task.BalancedProject(Project) - except Exception, e: - raise osv.except_osv(_('Error !'), e) - - for phase_id in phase_ids: - act_phase = phase_pool.browse(cr, uid, phase_id, context=context) - resources = act_phase.resource_ids - phase = eval("project.Phase_%d" % phase_id) - start_date = phase.start.to_datetime() - end_date = phase.end.to_datetime() - - if resources: - for res in resources: - vals = {} - vals.update({'date_start' : start_date }) - vals.update({'date_end' : end_date}) - resource_allocation_pool.write(cr, uid, res.id, vals, context=context) - if act_phase.task_ids: - for task in act_phase.task_ids: - vals = {} - #Getting values of the Tasks - temp = eval("phase.Task_%s"%task.id) - if temp.booked_resource: - res_name = temp.booked_resource[0].title - res_id = resource_pool.search(cr, uid,[('name','=',res_name)], context = context) - if res_id: - res = resource_pool.browse(cr, uid, res_id[0], context = context) - vals.update({'user_id' : res.user_id.id}) - - vals.update({'date_start' : temp.start.strftime('%Y-%m-%d %H:%M:%S')}) - vals.update({'date_end' : temp.end.strftime('%Y-%m-%d %H:%M:%S')}) - task_pool.write(cr, uid, task.id, vals, context=context) +""" % ( vacation, ) + return result - phase_pool.write(cr, uid, [phase_id], { - 'date_start': start_date.strftime('%Y-%m-%d'), - 'date_end': end_date.strftime('%Y-%m-%d') - }, context=context) - return True + def schedule_phases(self, cr, uid, ids, context=None): + context = context or {} + if type(ids) in (long, int,): + ids = [ids] + projects = self.browse(cr, uid, ids, context=context) + result = self._schedule_header(cr, uid, ids, context=context) + for project in projects: + result += self._schedule_project(cr, uid, project, context=context) + result += self.pool.get('project.phase').generate_phase(cr, uid, project.phase_ids, context=context) + + local_dict = {} + exec result in local_dict + projects_gantt = Task.BalancedProject(local_dict['Project']) + + for project in projects: + project_gantt = getattr(projects_gantt, 'Project_%d' % (project.id,)) + for phase in project.phase_ids: + if phase.state in ('done','cancelled'): + continue + # Maybe it's better to update than unlink/create if it already exists ? + p = getattr(project_gantt, 'Phase_%d' % (phase.id,)) + + self.pool.get('project.user.allocation').unlink(cr, uid, + [x.id for x in phase.user_ids], + context=context + ) + + for r in p.booked_resource: + self.pool.get('project.user.allocation').create(cr, uid, { + 'user_id': int(r.name[5:]), + 'phase_id': phase.id, + 'date_start': p.start.strftime('%Y-%m-%d %H:%M:%S'), + 'date_end': p.end.strftime('%Y-%m-%d %H:%M:%S') + }, context=context) + self.pool.get('project.phase').write(cr, uid, [phase.id], { + 'date_start': p.start.strftime('%Y-%m-%d %H:%M:%S'), + 'date_end': p.end.strftime('%Y-%m-%d %H:%M:%S') + }, context=context) + return True #TODO: DO Resource allocation and compute availability def compute_allocation(self, rc, uid, ids, start_date, end_date, context=None): @@ -584,186 +330,67 @@ def Project_%d(): return allocation def schedule_tasks(self, cr, uid, ids, context=None): - """ - Schedule task base on faces lib - """ - if context is None: - context = {} + context = context or {} if type(ids) in (long, int,): ids = [ids] - task_pool = self.pool.get('project.task') - resource_pool = self.pool.get('resource.resource') - data_pool = self.pool.get('ir.model.data') - data_model, day_uom_id = data_pool.get_object_reference(cr, uid, 'product', 'uom_day') + projects = self.browse(cr, uid, ids, context=context) + result = self._schedule_header(cr, uid, ids, context=context) + for project in projects: + result += self._schedule_project(cr, uid, project, context=context) + result += self.pool.get('project.task')._generate_task(cr, uid, project.tasks, ident=4, context=context) - for project in self.browse(cr, uid, ids, context=context): - calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False - start_date = project.date_start - - #Checking the Valid Phase resource allocation from project member - flag = False - res_missing = [] - members_ids = [] - if project.members: - members_ids = [user.id for user in project.members] - for phase in project.phase_ids: - if phase.resource_ids: - res_ids = [ re.id for re in phase.resource_ids] - for res in self.pool.get('project.resource.allocation').browse(cr, uid, res_ids, context=context): - if res.resource_id.user_id.id not in members_ids: - res_missing += [res.resource_id.name] - flag = True - if flag: - raise osv.except_osv(_('Warning !'),_("Resource(s) %s is(are) not member(s) of the project '%s' .") % (",".join(res_missing), project.name)) - #Creating resources using the member of the Project - u_ids = [i.id for i in project.members] - resource_objs = resource_pool.generate_resources(cr, uid, u_ids, calendar_id, context=context) - try: - start_date = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d") - except: - raise osv.except_osv(_('Error !'),_('Task Scheduling is not possible.\nProject should have the Start date for scheduling.')) - func_str = '' - start = start_date - minimum_time_unit = 1 - # default values - working_hours_per_day = 24 - working_days_per_week = 7 - working_days_per_month = 30 - working_days_per_year = 365 - - vacation = [] - if calendar_id: - working_hours_per_day = 8 #TODO: it should be come from calendars - working_days_per_week = 5 - working_days_per_month = 20 - working_days_per_year = 200 - vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context)) + local_dict = {} + exec result in local_dict + projects_gantt = Task.BalancedProject(local_dict['Project']) - working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context) - - cls_str = '' - # Creating Resources for the Project - for key, vals in resource_objs.items(): - cls_str +=''' - class Resource_%s(Resource): - title = \"%s\" - vacation = %s - efficiency = %s -'''%(key, vals.get('name',False), vals.get('vacation', False), vals.get('efficiency', False)) - - # Create a new project for each phase - func_str += ''' -def Project_%d(): - from resource.faces import Resource - title = \"%s\" - start = \'%s\' - minimum_time_unit = %s - working_hours_per_day = %s - working_days_per_week = %s - working_days_per_month = %s - working_days_per_year = %s - vacation = %s - working_days = %s -'''%(project.id, project.name, start, minimum_time_unit, working_hours_per_day, working_days_per_week, working_days_per_month, working_days_per_year, vacation, working_days ) - - parent = False - task_ids = [] - todo_task_ids = task_pool.search(cr, uid, [('project_id', '=', project.id), - ('state', 'in', ['draft', 'open', 'pending']) - ], order='sequence') - if todo_task_ids: - for task in task_pool.browse(cr, uid, todo_task_ids, context=context): - func_str += task_pool.generate_task(cr, uid, task.id, parent=parent, flag=True,context=context) - if not parent: - parent = task - task_ids.append(task.id) - func_str += cls_str + for project in projects: + project_gantt = getattr(projects_gantt, 'Project_%d' % (project.id,)) + for task in project.tasks: + if task.state in ('done','cancelled'): + continue - if not project.date_start:# or not project.members: - raise osv.except_osv(_('Error !'),_('Task Scheduling is not possible.\nProject should have the Start date for scheduling.')) - # Allocating Memory for the required Project and Phases and Resources - exec(func_str) - Project = eval('Project_%d' % project.id) - project = None - try: - project = Task.BalancedProject(Project) - except Exception, e: - raise osv.except_osv(_('Error !'), e) - - for task_id in task_ids: - task = eval("project.Task_%d" % task_id) - start_date = task.start.to_datetime() - end_date = task.end.to_datetime() - - task_pool.write(cr, uid, [task_id], { - 'date_start': start_date.strftime('%Y-%m-%d %H:%M:%S'), - 'date_end': end_date.strftime('%Y-%m-%d %H:%M:%S') - }, context=context) + p = getattr(project_gantt, 'Task_%d' % (task.id,)) + + self.pool.get('project.task').write(cr, uid, [task.id], { + 'date_start': p.start.strftime('%Y-%m-%d %H:%M:%S'), + 'date_end': p.end.strftime('%Y-%m-%d %H:%M:%S') + }, context=context) + if (not task.user_id) and (p.booked_resource): + self.pool.get('project.task').write(cr, uid, [task.id], { + 'user_id': int(p.booked_resource[0].name[5:]), + }, context=context) return True - project() -class resource_resource(osv.osv): - _inherit = "resource.resource" - def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): - if context is None: - context = {} - if context.get('project_id',False): - project_pool = self.pool.get('project.project') - project_rec = project_pool.browse(cr, uid, context['project_id'], context=context) - user_ids = [user_id.id for user_id in project_rec.members] - args.append(('user_id','in',user_ids)) - return super(resource_resource, self).search(cr, uid, args, offset, limit, order, context, count) - -resource_resource() - class project_task(osv.osv): _inherit = "project.task" _columns = { 'phase_id': fields.many2one('project.phase', 'Project Phase'), } - _defaults = { - 'user_id' : False - } + def _generate_task(self, cr, uid, tasks, ident=4, context=None): + context = context or {} + result = "" + ident = ' '*ident + for task in tasks: + if task.state in ('done','cancelled'): + continue + result += ''' +%sdef Task_%s(): +%s todo = \"%.2fH\" +%s effort = \"%.2fH\"''' % (ident,task.id, ident,task.remaining_hours, ident,task.total_hours) + start = [] + for t2 in task.parent_ids: + start.append("up.Task_%s.end" % (t2.id,)) + if start: + result += ''' +%s start = max(%s) +''' % (ident,','.join(start)) - def generate_task(self, cr, uid, task_id, parent=False, flag=False, context=None): - if context is None: - context = {} - task = self.browse(cr, uid, task_id, context=context) - duration = str(task.planned_hours)+ 'H' - str_resource = False - parent = task.parent_ids - if task.phase_id.resource_ids: - str_resource = ('%s | '*len(task.phase_id.resource_ids))[:-2] - str_resource = str_resource % tuple(map(lambda x: 'Resource_%s'%x.resource_id.id, task.phase_id.resource_ids)) - # Task Defination for the Phase of the Project - if not flag: - s = ''' - def Task_%s(): - title = \"%s\" - effort = \'%s\' - resource = %s -'''%(task.id, task.name, duration, str_resource) - if task.child_ids: - seq = [[child.planned_hours, child.id] for child in task.child_ids] - seq.sort(key=itemgetter(0)) - s +=''' - start = up.Task_%s.end - '''%(seq[-1][1]) - else: - s = ''' - def Task_%s(): - title = \"%s\" - effort = \'%s\' - resource = %s -'''%(task.id, task.name, duration, str_resource) - if task.child_ids: - seq = [[child.planned_hours, child.id] for child in task.child_ids] - seq.sort(key=itemgetter(0)) - s +=''' - start = up.Task_%s.end -'''%(seq[-1][1]) - s += '\n' - return s + if task.user_id: + result += ''' +%s resource = %s +''' % (ident, 'User_'+str(task.user_id.id)) + + result += "\n" + return result project_task() -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/project_long_term/project_long_term_demo.xml b/addons/project_long_term/project_long_term_demo.xml index 961b0c62c9b..252bcdd0e9d 100644 --- a/addons/project_long_term/project_long_term_demo.xml +++ b/addons/project_long_term/project_long_term_demo.xml @@ -1,55 +1,9 @@ - - - - - Analyst - - - - - - Project Manager - - - - - - Technical Leader - - - - - - Developer - - - - - - Designer - - - - - - Tester - - - - - - Project Finacial Manager - - - - - - + @@ -58,7 +12,6 @@ This Demo data file Human Resources, Phases and Resources,Tasks allocation and 30 - @@ -68,7 +21,6 @@ This Demo data file Human Resources, Phases and Resources,Tasks allocation and 20 - @@ -78,7 +30,6 @@ This Demo data file Human Resources, Phases and Resources,Tasks allocation and 20 - @@ -88,7 +39,6 @@ This Demo data file Human Resources, Phases and Resources,Tasks allocation and 90 - @@ -98,7 +48,6 @@ This Demo data file Human Resources, Phases and Resources,Tasks allocation and 30 - @@ -108,56 +57,9 @@ This Demo data file Human Resources, Phases and Resources,Tasks allocation and 10 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/project_long_term/project_long_term_view.xml b/addons/project_long_term/project_long_term_view.xml index 49b306ebfd5..dfef83e4468 100644 --- a/addons/project_long_term/project_long_term_view.xml +++ b/addons/project_long_term/project_long_term_view.xml @@ -2,32 +2,32 @@ - + # ------------------------------------------------------ - # Project Resource Allocation + # Project User Allocation # ------------------------------------------------------ - - project.resource.allocation.gantt - project.resource.allocation + + project.user.allocation.gantt + project.user.allocation gantt - + - - project.resource.allocation.calendar - project.resource.allocation + + project.user.allocation.calendar + project.user.allocation calendar - + @@ -35,53 +35,51 @@ - - project.resource.allocation.form - project.resource.allocation + + project.user.allocation.form + project.user.allocation form -
+ - + -
- - project.resource.allocation.list - project.resource.allocation + + project.user.allocation.list + project.user.allocation tree - - + + - - - project.resource.allocation.search - project.resource.allocation + + project.user.allocation.search + project.user.allocation search - + - + - + @@ -90,12 +88,12 @@ - Resources Allocation - project.resource.allocation + Planning of Users + project.user.allocation form gantt,tree,form,calendar {} - + @@ -110,32 +108,27 @@
- - - - - - - + + + + + - - + - - - - + + + + - - - + + - @@ -162,38 +155,9 @@ -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
-