diff --git a/addons/account/test/account_change_currency.yml b/addons/account/test/account_change_currency.yml
index 71a4ff6e301..4fe89534ce5 100644
--- a/addons/account/test/account_change_currency.yml
+++ b/addons/account/test/account_change_currency.yml
@@ -23,15 +23,6 @@
-
!assert {model: account.invoice, id: account_invoice_currency, string: Customer Invoice is in Draft state}:
- state == 'draft'
--
- I change the state of invoice to proforma2 by clicking PRO-FORMA button
--
- !workflow {model: account.invoice, action: invoice_proforma2, ref: account_invoice_currency}
--
- I check that now customer invoice is in the "proforma2" state
--
- !assert {model: account.invoice, id: account_invoice_currency}:
- - state == 'proforma2'
-
Now I select USD as new currency
-
@@ -49,6 +40,15 @@
"active_model": "account.invoice", "tz": False, "record_id": 4, "active_ids":
[ref("account_invoice_currency")], "type": "out_invoice", "active_id": ref("account_invoice_currency"),
})
+-
+ I change the state of invoice to proforma2 by clicking PRO-FORMA button
+-
+ !workflow {model: account.invoice, action: invoice_proforma2, ref: account_invoice_currency}
+-
+ I check that now customer invoice is in the "proforma2" state
+-
+ !assert {model: account.invoice, id: account_invoice_currency}:
+ - state == 'proforma2'
-
I can't change the currency of invoice when it is not in draft state so i change the state to cancel
-
diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py
index bf577ebeaf6..6bd2f2138ec 100644
--- a/addons/account_analytic_analysis/account_analytic_analysis.py
+++ b/addons/account_analytic_analysis/account_analytic_analysis.py
@@ -28,6 +28,210 @@ class account_analytic_account(osv.osv):
_name = "account.analytic.account"
_inherit = "account.analytic.account"
+ def _analysis_all(self, cr, uid, ids, fields, arg, context=None):
+ dp = 2
+ res = dict([(i, {}) for i in ids])
+
+ parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
+ accounts = self.browse(cr, uid, ids, context=context)
+
+ for f in fields:
+ if f == 'user_ids':
+ cr.execute('SELECT MAX(id) FROM res_users')
+ max_user = cr.fetchone()[0]
+ if parent_ids:
+ cr.execute('SELECT DISTINCT("user") FROM account_analytic_analysis_summary_user ' \
+ 'WHERE account_id IN %s AND unit_amount <> 0.0', (parent_ids,))
+ result = cr.fetchall()
+ else:
+ result = []
+ for id in ids:
+ res[id][f] = [int((id * max_user) + x[0]) for x in result]
+ elif f == 'month_ids':
+ if parent_ids:
+ cr.execute('SELECT DISTINCT(month_id) FROM account_analytic_analysis_summary_month ' \
+ 'WHERE account_id IN %s AND unit_amount <> 0.0', (parent_ids,))
+ result = cr.fetchall()
+ else:
+ result = []
+ for id in ids:
+ res[id][f] = [int(id * 1000000 + int(x[0])) for x in result]
+ elif f == 'last_worked_invoiced_date':
+ for id in ids:
+ res[id][f] = 0.0
+ if parent_ids:
+ cr.execute("select account_analytic_line.account_id, max(date) \
+ from account_analytic_line \
+ where account_id IN %s \
+ and invoice_id is not null \
+ GROUP BY account_analytic_line.account_id;", (parent_ids,))
+ for account_id, sum in cr.fetchall():
+ res[account_id][f] = sum
+ for account in accounts:
+ for child in account.child_ids:
+ if res[account.id].get(f, '') < res.get(child.id, {}).get(f, ''):
+ res[account.id][f] = res.get(child.id, {}).get(f, '')
+ elif f == 'ca_to_invoice':
+ for id in ids:
+ res[id][f] = 0.0
+ res2 = {}
+ if parent_ids:
+ # Amount uninvoiced hours to invoice at sale price
+ # Warning
+ # This computation doesn't take care of pricelist !
+ # Just consider list_price
+ cr.execute("""SELECT account_analytic_account.id, \
+ COALESCE(sum (product_template.list_price * \
+ account_analytic_line.unit_amount * \
+ ((100-hr_timesheet_invoice_factor.factor)/100)),0.0) \
+ AS ca_to_invoice \
+ FROM product_template \
+ join product_product \
+ on product_template.id = product_product.product_tmpl_id \
+ JOIN account_analytic_line \
+ on account_analytic_line.product_id = product_product.id \
+ JOIN account_analytic_journal \
+ on account_analytic_line.journal_id = account_analytic_journal.id \
+ JOIN account_analytic_account \
+ on account_analytic_account.id = account_analytic_line.account_id \
+ JOIN hr_timesheet_invoice_factor \
+ on hr_timesheet_invoice_factor.id = account_analytic_account.to_invoice \
+ WHERE account_analytic_account.id IN %s \
+ AND account_analytic_line.invoice_id is null \
+ AND account_analytic_line.to_invoice IS NOT NULL \
+ and account_analytic_journal.type in ('purchase','general') \
+ GROUP BY account_analytic_account.id;""", (parent_ids,))
+ for account_id, sum in cr.fetchall():
+ res[account_id][f] = round(sum, dp)
+
+ for account in accounts:
+ #res.setdefault(account.id, 0.0)
+ res2.setdefault(account.id, 0.0)
+ for child in account.child_ids:
+ if child.id != account.id:
+ res[account.id][f] += res.get(child.id, {}).get(f, 0.0)
+ res2[account.id] += res2.get(child.id, 0.0)
+ # sum both result on account_id
+ for id in ids:
+ res[id][f] = round(res.get(id, {}).get(f, 0.0), dp) + round(res2.get(id, 0.0), 2)
+ elif f == 'last_invoice_date':
+ for id in ids:
+ res[id][f] = ''
+ if parent_ids:
+ cr.execute ("select account_analytic_line.account_id, \
+ date(max(account_invoice.date_invoice)) \
+ from account_analytic_line \
+ join account_invoice \
+ on account_analytic_line.invoice_id = account_invoice.id \
+ where account_analytic_line.account_id IN %s \
+ and account_analytic_line.invoice_id is not null \
+ GROUP BY account_analytic_line.account_id",(parent_ids,))
+ for account_id, sum in cr.fetchall():
+ res[account_id] = sum
+ for account in accounts:
+ for child in account.child_ids:
+ if res[account.id][f] < res.get(child.id, {}).get(f, ''):
+ res[account.id][f] = res.get(child.id, {}).get(f, '')
+ elif f == 'last_worked_date':
+ for id in ids:
+ res[id][f] = ''
+ if parent_ids:
+ cr.execute("select account_analytic_line.account_id, max(date) \
+ from account_analytic_line \
+ where account_id IN %s \
+ and invoice_id is null \
+ GROUP BY account_analytic_line.account_id" ,(parent_ids,))
+ for account_id, sum in cr.fetchall():
+ res[account_id][f] = sum
+ for account in accounts:
+ for child in account.child_ids:
+ if res[account.id][f] < res.get(child.id, {}).get(f, ''):
+ res[account.id][f] = res.get(child.id, {}).get(f, '')
+ elif f == 'hours_qtt_non_invoiced':
+ for id in ids:
+ res[id][f] = 0.0
+ if parent_ids:
+ cr.execute("select account_analytic_line.account_id, COALESCE(sum(unit_amount),0.0) \
+ from account_analytic_line \
+ join account_analytic_journal \
+ on account_analytic_line.journal_id = account_analytic_journal.id \
+ where account_analytic_line.account_id IN %s \
+ and account_analytic_journal.type='general' \
+ and invoice_id is null \
+ AND to_invoice IS NOT NULL \
+ GROUP BY account_analytic_line.account_id;",(parent_ids,))
+ for account_id, sum in cr.fetchall():
+ res[account_id][f] = round(sum, dp)
+ for account in accounts:
+ for child in account.child_ids:
+ if account.id != child.id:
+ res[account.id][f] += res.get(child.id, {}).get(f, 0.0)
+ for id in ids:
+ res[id][f] = round(res[id][f], dp)
+ elif f == 'hours_quantity':
+ for id in ids:
+ res[id][f] = 0.0
+ if parent_ids:
+ cr.execute("select account_analytic_line.account_id,COALESCE(SUM(unit_amount),0.0) \
+ from account_analytic_line \
+ join account_analytic_journal \
+ on account_analytic_line.journal_id = account_analytic_journal.id \
+ where account_analytic_line.account_id IN %s \
+ and account_analytic_journal.type='general' \
+ GROUP BY account_analytic_line.account_id",(parent_ids,))
+ ff = cr.fetchall()
+ for account_id, sum in ff:
+ res[account_id][f] = round(sum, dp)
+ for account in accounts:
+ for child in account.child_ids:
+ if account.id != child.id:
+ res[account.id][f] += res.get(child.id, {}).get(f, 0.0)
+ for id in ids:
+ res[id][f] = round(res[id][f], dp)
+ elif f == 'ca_theorical':
+ # TODO Take care of pricelist and purchase !
+ for id in ids:
+ res[id][f] = 0.0
+ res2 = {}
+ # Warning
+ # This computation doesn't take care of pricelist !
+ # Just consider list_price
+ if parent_ids:
+ cr.execute("""select account_analytic_line.account_id as account_id, \
+ COALESCE(sum((account_analytic_line.unit_amount * pt.list_price) \
+ - (account_analytic_line.unit_amount * pt.list_price \
+ * hr.factor)),0.0) as somme
+ from account_analytic_line \
+ left join account_analytic_journal \
+ on (account_analytic_line.journal_id = account_analytic_journal.id) \
+ join product_product pp \
+ on (account_analytic_line.product_id = pp.id) \
+ join product_template pt \
+ on (pp.product_tmpl_id = pt.id) \
+ join account_analytic_account a \
+ on (a.id=account_analytic_line.account_id) \
+ join hr_timesheet_invoice_factor hr \
+ on (hr.id=a.to_invoice) \
+ where account_analytic_line.account_id IN %s \
+ and a.to_invoice IS NOT NULL \
+ and account_analytic_journal.type IN ('purchase','general')
+ GROUP BY account_analytic_line.account_id""",(parent_ids,))
+ for account_id, sum in cr.fetchall():
+ res2[account_id] = round(sum, dp)
+
+ for account in accounts:
+ res2.setdefault(account.id, 0.0)
+ for child in account.child_ids:
+ if account.id != child.id:
+ res[account.id][f] += res.get(child.id, {}).get(f, 0.0)
+ res[account.id][f] += res2.get(child.id, 0.0)
+
+ # sum both result on account_id
+ for id in ids:
+ res[id][f] = round(res[id][f], dp) + round(res2.get(id, 0.0), dp)
+
+ return res
+
def _ca_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
res = {}
parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
@@ -44,101 +248,6 @@ class account_analytic_account(osv.osv):
return self._compute_currency_for_level_tree(cr, uid, ids, parent_ids, res, context)
- def _ca_to_invoice_calc(self, cr, uid, ids, name, arg, context=None):
- res = {}
- res2 = {}
- parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
- if parent_ids:
- # Amount uninvoiced hours to invoice at sale price
- # Warning
- # This computation doesn't take care of pricelist !
- # Just consider list_price
- cr.execute("""SELECT account_analytic_account.id, \
- COALESCE(sum (product_template.list_price * \
- account_analytic_line.unit_amount * \
- ((100-hr_timesheet_invoice_factor.factor)/100)),0.0) \
- AS ca_to_invoice \
- FROM product_template \
- join product_product \
- on product_template.id = product_product.product_tmpl_id \
- JOIN account_analytic_line \
- on account_analytic_line.product_id = product_product.id \
- JOIN account_analytic_journal \
- on account_analytic_line.journal_id = account_analytic_journal.id \
- JOIN account_analytic_account \
- on account_analytic_account.id = account_analytic_line.account_id \
- JOIN hr_timesheet_invoice_factor \
- on hr_timesheet_invoice_factor.id = account_analytic_account.to_invoice \
- WHERE account_analytic_account.id IN %s \
- AND account_analytic_line.invoice_id is null \
- AND account_analytic_line.to_invoice IS NOT NULL \
- and account_analytic_journal.type in ('purchase','general') \
- GROUP BY account_analytic_account.id;""",(parent_ids,))
- for account_id, sum in cr.fetchall():
- res[account_id] = round(sum,2)
-
- for obj_id in ids:
- res.setdefault(obj_id, 0.0)
- res2.setdefault(obj_id, 0.0)
- for child_id in self.search(cr, uid,
- [('parent_id', 'child_of', [obj_id])]):
- if child_id != obj_id:
- res[obj_id] += res.get(child_id, 0.0)
- res2[obj_id] += res2.get(child_id, 0.0)
- # sum both result on account_id
- for id in ids:
- res[id] = round(res.get(id, 0.0),2) + round(res2.get(id, 0.0),2)
- return res
-
- def _hours_qtt_non_invoiced_calc (self, cr, uid, ids, name, arg, context=None):
- res = {}
- parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
- if parent_ids:
- cr.execute("select account_analytic_line.account_id, COALESCE(sum(unit_amount),0.0) \
- from account_analytic_line \
- join account_analytic_journal \
- on account_analytic_line.journal_id = account_analytic_journal.id \
- where account_analytic_line.account_id IN %s \
- and account_analytic_journal.type='general' \
- and invoice_id is null \
- AND to_invoice IS NOT NULL \
- GROUP BY account_analytic_line.account_id;",(parent_ids,))
- for account_id, sum in cr.fetchall():
- res[account_id] = round(sum,2)
- for obj_id in ids:
- res.setdefault(obj_id, 0.0)
- for child_id in self.search(cr, uid,
- [('parent_id', 'child_of', [obj_id])]):
- if child_id != obj_id:
- res[obj_id] += res.get(child_id, 0.0)
- for id in ids:
- res[id] = round(res.get(id, 0.0),2)
- return res
-
- def _hours_quantity_calc(self, cr, uid, ids, name, arg, context=None):
- res = {}
- parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
- if parent_ids:
- cr.execute("select account_analytic_line.account_id,COALESCE(SUM(unit_amount),0.0) \
- from account_analytic_line \
- join account_analytic_journal \
- on account_analytic_line.journal_id = account_analytic_journal.id \
- where account_analytic_line.account_id IN %s \
- and account_analytic_journal.type='general' \
- GROUP BY account_analytic_line.account_id",(parent_ids,))
- ff = cr.fetchall()
- for account_id, sum in ff:
- res[account_id] = round(sum,2)
- for obj_id in ids:
- res.setdefault(obj_id, 0.0)
- for child_id in self.search(cr, uid,
- [('parent_id', 'child_of', [obj_id])]):
- if child_id != obj_id:
- res[obj_id] += res.get(child_id, 0.0)
- for id in ids:
- res[id] = round(res.get(id, 0.0),2)
- return res
-
def _total_cost_calc(self, cr, uid, ids, name, arg, context=None):
res = {}
parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
@@ -155,117 +264,6 @@ class account_analytic_account(osv.osv):
res[account_id] = round(sum,2)
return self._compute_currency_for_level_tree(cr, uid, ids, parent_ids, res, context)
- # TODO Take care of pricelist and purchase !
- def _ca_theorical_calc(self, cr, uid, ids, name, arg, context=None):
- res = {}
- res2 = {}
- parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
- # Warning
- # This computation doesn't take care of pricelist !
- # Just consider list_price
- if parent_ids:
- cr.execute("""select account_analytic_line.account_id as account_id, \
- COALESCE(sum((account_analytic_line.unit_amount * pt.list_price) \
- - (account_analytic_line.unit_amount * pt.list_price \
- * hr.factor)),0.0) as somme
- from account_analytic_line \
- left join account_analytic_journal \
- on (account_analytic_line.journal_id = account_analytic_journal.id) \
- join product_product pp \
- on (account_analytic_line.product_id = pp.id) \
- join product_template pt \
- on (pp.product_tmpl_id = pt.id) \
- join account_analytic_account a \
- on (a.id=account_analytic_line.account_id) \
- join hr_timesheet_invoice_factor hr \
- on (hr.id=a.to_invoice) \
- where account_analytic_line.account_id IN %s \
- and a.to_invoice IS NOT NULL \
- and account_analytic_journal.type IN ('purchase','general')
- GROUP BY account_analytic_line.account_id""",(parent_ids,))
- for account_id, sum in cr.fetchall():
- res2[account_id] = round(sum,2)
-
- for obj_id in ids:
- res.setdefault(obj_id, 0.0)
- res2.setdefault(obj_id, 0.0)
- for child_id in self.search(cr, uid,
- [('parent_id', 'child_of', [obj_id])]):
- if child_id != obj_id:
- res[obj_id] += res.get(child_id, 0.0)
- res[obj_id] += res2.get(child_id, 0.0)
-
- # sum both result on account_id
- for id in ids:
- res[id] = round(res.get(id, 0.0),2) + round(res2.get(id, 0.0),2)
- return res
-
- def _last_worked_date_calc (self, cr, uid, ids, name, arg, context=None):
- res = {}
- parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
- if parent_ids:
- cr.execute("select account_analytic_line.account_id, max(date) \
- from account_analytic_line \
- where account_id IN %s \
- and invoice_id is null \
- GROUP BY account_analytic_line.account_id" ,(parent_ids,))
- for account_id, sum in cr.fetchall():
- res[account_id] = sum
- for obj_id in ids:
- res.setdefault(obj_id, '')
- for child_id in self.search(cr, uid,
- [('parent_id', 'child_of', [obj_id])]):
- if res[obj_id] < res.get(child_id, ''):
- res[obj_id] = res.get(child_id, '')
- for id in ids:
- res[id] = res.get(id, '')
- return res
-
- def _last_invoice_date_calc (self, cr, uid, ids, name, arg, context=None):
- res = {}
- parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
- if parent_ids:
- cr.execute ("select account_analytic_line.account_id, \
- date(max(account_invoice.date_invoice)) \
- from account_analytic_line \
- join account_invoice \
- on account_analytic_line.invoice_id = account_invoice.id \
- where account_analytic_line.account_id IN %s \
- and account_analytic_line.invoice_id is not null \
- GROUP BY account_analytic_line.account_id",(parent_ids,))
- for account_id, sum in cr.fetchall():
- res[account_id] = sum
- for obj_id in ids:
- res.setdefault(obj_id, '')
- for child_id in self.search(cr, uid,
- [('parent_id', 'child_of', [obj_id])]):
- if res[obj_id] < res.get(child_id, ''):
- res[obj_id] = res.get(child_id, '')
- for id in ids:
- res[id] = res.get(id, '')
- return res
-
- def _last_worked_invoiced_date_calc (self, cr, uid, ids, name, arg, context=None):
- res = {}
- parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
- if parent_ids:
- cr.execute("select account_analytic_line.account_id, max(date) \
- from account_analytic_line \
- where account_id IN %s \
- and invoice_id is not null \
- GROUP BY account_analytic_line.account_id;",(parent_ids,))
- for account_id, sum in cr.fetchall():
- res[account_id] = sum
- for obj_id in ids:
- res.setdefault(obj_id, '')
- for child_id in self.search(cr, uid,
- [('parent_id', 'child_of', [obj_id])]):
- if res[obj_id] < res.get(child_id, ''):
- res[obj_id] = res.get(child_id, '')
- for id in ids:
- res[id] = res.get(id, '')
- return res
-
def _remaining_hours_calc(self, cr, uid, ids, name, arg, context=None):
res = {}
for account in self.browse(cr, uid, ids):
@@ -338,42 +336,16 @@ class account_analytic_account(osv.osv):
res[id] = round(res.get(id, 0.0),2)
return res
- def _month(self, cr, uid, ids, name, arg, context=None):
- res = {}
- for id in ids:
- parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
- if parent_ids:
- cr.execute('SELECT DISTINCT(month_id) FROM account_analytic_analysis_summary_month ' \
- 'WHERE account_id IN %s AND unit_amount <> 0.0',(parent_ids,))
- res[id] = [int(id * 1000000 + int(x[0])) for x in cr.fetchall()]
- else:
- res[id] = []
- return res
-
- def _user(self, cr, uid, ids, name, arg, context=None):
- res = {}
- cr.execute('SELECT MAX(id) FROM res_users')
- max_user = cr.fetchone()[0]
- for id in ids:
- parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
- if parent_ids:
- cr.execute('SELECT DISTINCT("user") FROM account_analytic_analysis_summary_user ' \
- 'WHERE account_id IN %s AND unit_amount <> 0.0',(parent_ids,))
- res[id] = [int((id * max_user) + x[0]) for x in cr.fetchall()]
- else:
- res[id] = []
- return res
-
_columns ={
'ca_invoiced': fields.function(_ca_invoiced_calc, method=True, type='float', string='Invoiced Amount', help="Total customer invoiced amount for this account."),
'total_cost': fields.function(_total_cost_calc, method=True, type='float', string='Total Costs', help="Total of costs for this account. It includes real costs (from invoices) and indirect costs, like time spent on timesheets."),
- 'ca_to_invoice': fields.function(_ca_to_invoice_calc, method=True, type='float', string='Uninvoiced Amount', help="If invoice from analytic account, the remaining amount you can invoice to the customer based on the total costs."),
- 'ca_theorical': fields.function(_ca_theorical_calc, method=True, type='float', string='Theorical Revenue', help="Based on the costs you had on the project, what would have been the revenue if all these costs have been invoiced at the normal sale price provided by the pricelist."),
- 'hours_quantity': fields.function(_hours_quantity_calc, method=True, type='float', string='Hours Tot', help="Number of hours you spent on the analytic account (from timesheet). It computes on all journal of type 'general'."),
- 'last_invoice_date': fields.function(_last_invoice_date_calc, method=True, type='date', string='Last Invoice Date', help="Date of the last invoice created for this analytic account."),
- 'last_worked_invoiced_date': fields.function(_last_worked_invoiced_date_calc, method=True, type='date', string='Date of Last Invoiced Cost', help="If invoice from the costs, this is the date of the latest work or cost that have been invoiced."),
- 'last_worked_date': fields.function(_last_worked_date_calc, method=True, type='date', string='Date of Last Cost/Work', help="Date of the latest work done on this account."),
- 'hours_qtt_non_invoiced': fields.function(_hours_qtt_non_invoiced_calc, method=True, type='float', string='Uninvoiced Hours', help="Number of hours (from journal of type 'general') that can be invoiced if you invoice based on analytic account."),
+ 'ca_to_invoice': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='float', string='Uninvoiced Amount', help="If invoice from analytic account, the remaining amount you can invoice to the customer based on the total costs."),
+ 'ca_theorical': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='float', string='Theorical Revenue', help="Based on the costs you had on the project, what would have been the revenue if all these costs have been invoiced at the normal sale price provided by the pricelist."),
+ 'hours_quantity': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='float', string='Hours Tot', help="Number of hours you spent on the analytic account (from timesheet). It computes on all journal of type 'general'."),
+ 'last_invoice_date': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='date', string='Last Invoice Date', help="Date of the last invoice created for this analytic account."),
+ 'last_worked_invoiced_date': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='date', string='Date of Last Invoiced Cost', help="If invoice from the costs, this is the date of the latest work or cost that have been invoiced."),
+ 'last_worked_date': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='date', string='Date of Last Cost/Work', help="Date of the latest work done on this account."),
+ 'hours_qtt_non_invoiced': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='float', string='Uninvoiced Hours', help="Number of hours (from journal of type 'general') that can be invoiced if you invoice based on analytic account."),
'hours_qtt_invoiced': fields.function(_hours_qtt_invoiced_calc, method=True, type='float', string='Invoiced Hours', help="Number of hours that can be invoiced plus those that already have been invoiced."),
'remaining_hours': fields.function(_remaining_hours_calc, method=True, type='float', string='Remaining Hours', help="Computed using the formula: Maximum Quantity - Hours Tot."),
'remaining_ca': fields.function(_remaining_ca_calc, method=True, type='float', string='Remaining Revenue', help="Computed using the formula: Max Invoice Price - Invoiced Amount."),
@@ -381,8 +353,8 @@ class account_analytic_account(osv.osv):
'real_margin': fields.function(_real_margin_calc, method=True, type='float', string='Real Margin', help="Computed using the formula: Invoiced Amount - Total Costs."),
'theorical_margin': fields.function(_theorical_margin_calc, method=True, type='float', string='Theorical Margin', help="Computed using the formula: Theorial Revenue - Total Costs"),
'real_margin_rate': fields.function(_real_margin_rate_calc, method=True, type='float', string='Real Margin Rate (%)', help="Computes using the formula: (Real Margin / Total Costs) * 100."),
- 'month_ids': fields.function(_month, method=True, type='many2many', relation='account_analytic_analysis.summary.month', string='Month'),
- 'user_ids': fields.function(_user, method=True, type="many2many", relation='account_analytic_analysis.summary.user', string='User'),
+ 'month_ids': fields.function(_analysis_all, method=True, multi='analytic_analysis', type='many2many', relation='account_analytic_analysis.summary.month', string='Month'),
+ 'user_ids': fields.function(_analysis_all, method=True, multi='analytic_analysis', type="many2many", relation='account_analytic_analysis.summary.user', string='User'),
}
account_analytic_account()
diff --git a/addons/account_reporting/report/account_report_bs.rml b/addons/account_reporting/report/account_report_bs.rml
index b33ebb4bcc8..c0fa4f1e2d2 100644
--- a/addons/account_reporting/report/account_report_bs.rml
+++ b/addons/account_reporting/report/account_report_bs.rml
@@ -155,7 +155,7 @@
[[ company.currency_id.name ]]
- [[ setTag('font','font',{'face':o.font_style,'color':o.color_font.name,'backColor':o.color_back.name}) ]] [[o.name]]
+ [[ setTag('font','font',{'face':o.font_style or "Helvetica",'color':o.color_font.name or "black",'backColor':o.color_back.name or "white"}) ]] [[o.name]]
diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py
index eef25f665d8..b97edb9a546 100644
--- a/addons/analytic/analytic.py
+++ b/addons/analytic/analytic.py
@@ -35,16 +35,16 @@ class account_analytic_account(osv.osv):
cr.execute("SELECT a.id, r.currency_id FROM account_analytic_account a INNER JOIN res_company r ON (a.company_id = r.id) where a.id IN %s" , (tuple(ids2),))
currency = dict(cr.fetchall())
res_currency= self.pool.get('res.currency')
- for id in ids:
- if id not in ids2:
+ for account in self.browse(cr, uid, ids, context=context):
+ if account.id not in ids2:
continue
- for child in self.search(cr, uid, [('parent_id', 'child_of', [id])]):
- if child != id:
- res.setdefault(id, 0.0)
- if currency[child]!=currency[id]:
- res[id] += res_currency.compute(cr, uid, currency[child], currency[id], res.get(child, 0.0), context=context)
+ for child in account.child_ids:
+ if child.id != account.id:
+ res.setdefault(account.id, 0.0)
+ if currency[child.id] != currency[account.id]:
+ res[account.id] += res_currency.compute(cr, uid, currency[child.id], currency[account.id], res.get(child.id, 0.0), context=context)
else:
- res[id] += res.get(child, 0.0)
+ res[account.id] += res.get(child.id, 0.0)
cur_obj = res_currency.browse(cr, uid, currency.values(), context=context)
cur_obj = dict([(o.id, o) for o in cur_obj])
@@ -145,13 +145,13 @@ class account_analytic_account(osv.osv):
for account_id, sum in cr.fetchall():
res[account_id] = sum
- for id in ids:
- if id not in parent_ids:
+ for account in self.browse(cr, uid, ids, context=context):
+ if account.id not in parent_ids:
continue
- for child in self.search(cr, uid, [('parent_id', 'child_of', [id])]):
- if child != id:
- res.setdefault(id, 0.0)
- res[id] += res.get(child, 0.0)
+ for child in account.child_ids:
+ if child.id != account.id:
+ res.setdefault(account.id, 0.0)
+ res[account.id] += res.get(child.id, 0.0)
return dict([(i, res[i]) for i in ids])
def name_get(self, cr, uid, ids, context=None):
diff --git a/addons/crm/report/crm_phonecall_report_view.xml b/addons/crm/report/crm_phonecall_report_view.xml
index 20ad0668fc6..33d630e471b 100644
--- a/addons/crm/report/crm_phonecall_report_view.xml
+++ b/addons/crm/report/crm_phonecall_report_view.xml
@@ -89,14 +89,17 @@
+ string="Todo"
+ domain="[('state','in',('draft','open'))]"
+ help="Phone calls which are in draft and open state"/>
-
+ string="Not Held"
+ domain="[('state','=','pending')]"
+ help="Phone calls which are in pending state"/>
+
-
@@ -145,7 +147,6 @@
-
diff --git a/addons/hr/hr_department.py b/addons/hr/hr_department.py
index 21946469ce4..ec6f276e670 100644
--- a/addons/hr/hr_department.py
+++ b/addons/hr/hr_department.py
@@ -113,87 +113,10 @@ class res_users(osv.osv):
_inherit = 'res.users'
_description = 'User'
- def _parent_compute(self, cr, uid, ids, name, args, context=None):
- if context is None:
- context = {}
- result = {}
- obj_dept = self.pool.get('hr.department')
- for user_id in ids:
- emp_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', user_id)], context=context)
- cr.execute('SELECT emp.department_id FROM hr_employee AS emp \
- JOIN resource_resource AS res ON res.id = emp.resource_id \
- JOIN hr_department as dept ON dept.id = emp.department_id \
- WHERE res.user_id = %s AND emp.department_id IS NOT NULL AND dept.manager_id IS NOT NULL', (user_id,))
- ids_dept = [x[0] for x in cr.fetchall()]
- parent_ids = []
- if ids_dept:
- data_dept = obj_dept.read(cr, uid, ids_dept, ['manager_id'], context=context)
- parent_ids = map(lambda x: x['manager_id'][0], data_dept)
- cr.execute('SELECT res.user_id FROM hr_employee AS emp JOIN resource_resource AS res ON res.id=emp.resource_id \
- WHERE emp.id IN %s AND res.user_id IS NOT NULL', (tuple(parent_ids),))
- parent_ids = [x[0] for x in cr.fetchall()]
- result[user_id] = parent_ids
- return result
-
- def _parent_search(self, cr, uid, obj, name, args, context=None):
- if context is None:
- context = {}
- parent = []
- for arg in args:
- if arg[0] == 'parent_id':
- parent = arg[2]
- child_ids = self._child_compute(cr, uid, parent, name, args, context=context)
- if not child_ids:
- return [('id', 'in', [0])]
- return [('id', 'in', child_ids.get(uid, []))]
-
- def _child_compute(self, cr, uid, ids, name, args, context=None):
- if context is None:
- context = {}
- obj_dept = self.pool.get('hr.department')
- obj_user = self.pool.get('res.users')
- result = {}
- for user_id in ids:
- child_ids = []
- cr.execute('SELECT dept.id FROM hr_department AS dept \
- LEFT JOIN hr_employee AS emp ON dept.manager_id = emp.id \
- WHERE emp.id IN \
- (SELECT emp.id FROM hr_employee \
- JOIN resource_resource r ON r.id = emp.resource_id WHERE r.user_id=' + str(user_id) + ') ')
- mgnt_dept_ids = [x[0] for x in cr.fetchall()]
- ids_dept = obj_dept.search(cr, uid, [('id', 'child_of', mgnt_dept_ids)], context=context)
- if ids_dept:
- data_dept = obj_dept.read(cr, uid, ids_dept, ['member_ids'], context=context)
- childs = map(lambda x: x['member_ids'], data_dept)
- childs = tools.flatten(childs)
- childs = obj_user.search(cr, uid, [('id', 'in', childs),('active', '=', True)], context=context)
- if user_id in childs:
- childs.remove(user_id)
- child_ids.extend(tools.flatten(childs))
- set = {}
- map(set.__setitem__, child_ids, [])
- child_ids = set.keys()
- result[user_id] = child_ids
- return result
-
- def _child_search(self, cr, uid, obj, name, args, context=None):
- if context is None:
- context = {}
- parent = []
- for arg in args:
- if arg[0] == 'child_ids':
- parent = arg[2]
- child_ids = self._child_compute(cr, uid, parent, name, args, context=context)
- if not child_ids:
- return [('id', 'in', [0])]
- return [('id', 'in', child_ids.get(uid, []))]
-
_columns = {
- 'parent_id': fields.function(_parent_compute, relation='res.users', fnct_search=_parent_search, method=True, string="Managers", type='many2many'),
- 'child_ids': fields.function(_child_compute, relation='res.users', fnct_search=_child_search, method=True, string="Subordinates", type='many2many'),
'context_department_id': fields.many2one('hr.department', 'Departments'),
}
res_users()
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/hr/hr_department_view.xml b/addons/hr/hr_department_view.xml
index c2bb6ac7390..7626c501167 100644
--- a/addons/hr/hr_department_view.xml
+++ b/addons/hr/hr_department_view.xml
@@ -87,10 +87,6 @@
-
-
-
-
diff --git a/addons/hr_expense/report/expense.rml b/addons/hr_expense/report/expense.rml
index bb9a1ba0b1c..4ae45ad0aa2 100644
--- a/addons/hr_expense/report/expense.rml
+++ b/addons/hr_expense/report/expense.rml
@@ -259,7 +259,7 @@
- [[ o.note or '' ]] [[ setTag('para','xpre') ]]
+ [[ setTag('para','xpre') ]] [[ o.note or '' ]]
diff --git a/addons/hr_payroll/hr_payroll.py b/addons/hr_payroll/hr_payroll.py
index 7a6eaf14b58..48ffc1f6b20 100644
--- a/addons/hr_payroll/hr_payroll.py
+++ b/addons/hr_payroll/hr_payroll.py
@@ -784,6 +784,7 @@ class hr_payslip(osv.osv):
'contract_id':fields.many2one('hr.contract', 'Contract', required=False),
'igross': fields.float('Calculaton Field', readonly=True, digits=(16, 2), help="Calculation field used for internal calculation, do not place this on form"),
'inet': fields.float('Calculaton Field', readonly=True, digits=(16, 2), help="Calculation field used for internal calculation, do not place this on form"),
+ 'period_id': fields.many2one('account.period', 'Force Period', domain=[('state','<>','done')], help="Keep empty to use the period of the validation(Payslip) date."),
}
_defaults = {
'date': lambda *a: time.strftime('%Y-%m-%d'),
diff --git a/addons/hr_payroll/hr_payroll_view.xml b/addons/hr_payroll/hr_payroll_view.xml
index fbfa2d1aad5..d833612e383 100644
--- a/addons/hr_payroll/hr_payroll_view.xml
+++ b/addons/hr_payroll/hr_payroll_view.xml
@@ -500,12 +500,12 @@
+
-
diff --git a/addons/hr_timesheet/report/user_timesheet.py b/addons/hr_timesheet/report/user_timesheet.py
index 8cf255c9b7f..dc048748f46 100644
--- a/addons/hr_timesheet/report/user_timesheet.py
+++ b/addons/hr_timesheet/report/user_timesheet.py
@@ -77,7 +77,7 @@ class report_custom(report_rml):
%.2f
'''
- rpt_obj = pooler.get_pool(cr.dbname).get('hr.holidays')
+ rpt_obj = pooler.get_pool(cr.dbname).get('hr.employee')
rml_obj=report_sxw.rml_parse(cr, uid, rpt_obj._name,context)
header_xml = '''
diff --git a/addons/hr_timesheet/report/users_timesheet.py b/addons/hr_timesheet/report/users_timesheet.py
index 0198994728c..35c86fd08b9 100644
--- a/addons/hr_timesheet/report/users_timesheet.py
+++ b/addons/hr_timesheet/report/users_timesheet.py
@@ -97,7 +97,7 @@ class report_custom(report_rml):
# Computing the xml
#Without this, report don't show non-ascii characters (TO CHECK)
date_xml = '\n'.join(date_xml)
- rpt_obj = pooler.get_pool(cr.dbname).get('hr.holidays')
+ rpt_obj = pooler.get_pool(cr.dbname).get('hr.employee')
rml_obj=report_sxw.rml_parse(cr, uid, rpt_obj._name,context)
header_xml = '''
diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
index cc6137313b1..ed1c8394de1 100644
--- a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
+++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
@@ -44,17 +44,22 @@ class account_analytic_account(osv.osv):
if context is None:
context = {}
res = {}
+
+ cr.execute('select account_id as account_id, l.invoice_id from hr_analytic_timesheet h left join account_analytic_line l on (h.line_id=l.id)')
+ account_to_invoice_map = {}
+ for rec in cr.dictfetchall():
+ account_to_invoice_map.setdefault(rec['account_id'], []).append(rec['invoice_id'])
+
for account in self.browse(cr, uid, ids, context=context):
invoiced = {}
- cr.execute('select distinct(l.invoice_id) from hr_analytic_timesheet h left join account_analytic_line l on (h.line_id=l.id) where account_id=%s', (account.id,))
- invoice_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
+ invoice_ids = filter(None, list(set(account_to_invoice_map.get(account.id, []))))
for invoice in obj_invoice.browse(cr, uid, invoice_ids, context=context):
res.setdefault(account.id, 0.0)
res[account.id] += invoice.amount_untaxed
for id in ids:
res[id] = round(res.get(id, 0.0),2)
- return res
+ return res
_inherit = "account.analytic.account"
_columns = {
diff --git a/addons/mrp/mrp_demo.xml b/addons/mrp/mrp_demo.xml
index f454a8d14a8..e303ceb3378 100644
--- a/addons/mrp/mrp_demo.xml
+++ b/addons/mrp/mrp_demo.xml
@@ -14,7 +14,12 @@
Repairs workshop
-
+
+
+
+
diff --git a/addons/project_long_term/project_long_term.py b/addons/project_long_term/project_long_term.py
index b795dda557e..79837416d97 100644
--- a/addons/project_long_term/project_long_term.py
+++ b/addons/project_long_term/project_long_term.py
@@ -21,9 +21,10 @@
import time
from datetime import date, datetime, timedelta
-
from tools.translate import _
from osv import fields, osv
+from resource.faces import task as Task
+import operator
class project_phase(osv.osv):
_name = "project.phase"
@@ -181,38 +182,48 @@ class project_phase(osv.osv):
uom_obj = self.pool.get('product.uom')
if context is None:
context = {}
+ res = super(project_phase, self).write(cr, uid, ids, vals, context=context)
if context.get('scheduler',False):
- return super(project_phase, self).write(cr, uid, ids, vals, context=context)
+ return res
# Consider calendar and efficiency if the phase is performed by a resource
# otherwise consider the project's working calendar
+
+ #TOCHECK : why need this ?
if isinstance(ids, (int, long)):
ids = [ids]
- phase = self.browse(cr, uid, ids[0], context=context)
- 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:
+ default_uom_id = self._get_default_uom_id(cr, uid)
+ for phase in self.browse(cr, uid, ids, context=context):
+ 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:
cal_id = resource_obj.browse(cr, uid, resource_id[0], context=context).calendar_id.id
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)
- # Change the date_start and date_end
- # for previous and next phases respectively based on valid condition
- if vals.get('date_start', False) and vals['date_start'] < phase.date_start:
+
+ avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
+ # Change the date_start and date_end
+ # for previous and next phases respectively based on valid condition
+ if vals.get('date_start', False) and vals['date_start'] < phase.date_start:
dt_start = datetime.strptime(vals['date_start'], '%Y-%m-%d')
work_times = resource_calendar_obj.interval_get(cr, uid, calendar_id, dt_start, avg_hours or 0.0, resource_id and resource_id[0] or False)
if work_times:
vals['date_end'] = work_times[-1][1].strftime('%Y-%m-%d')
for prv_phase in phase.previous_phase_ids:
+ if prv_phase.id == phase.id:
+ continue
self._check_date_start(cr, uid, prv_phase, dt_start, context=context)
- if vals.get('date_end', False) and vals['date_end'] > phase.date_end:
+
+ if vals.get('date_end', False) and vals['date_end'] > phase.date_end:
dt_end = datetime.strptime(vals['date_end'], '%Y-%m-%d')
work_times = resource_calendar_obj.interval_min_get(cr, uid, calendar_id, dt_end, avg_hours or 0.0, resource_id and resource_id[0] or False)
if work_times:
vals['date_start'] = work_times[0][0].strftime('%Y-%m-%d')
for next_phase in phase.next_phase_ids:
+ if next_phase.id == phase.id:
+ continue
self._check_date_end(cr, uid, next_phase, dt_end, context=context)
- return super(project_phase, self).write(cr, uid, ids, vals, context=context)
+
+ return res
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
@@ -241,6 +252,118 @@ class project_phase(osv.osv):
self.write(cr, uid, ids, {'state': 'done'})
return True
+ def generate_resources(self, cr, uid, ids, context=None):
+ """
+ Return a list of Resource Class objects for the resources allocated to the phase.
+ """
+ res = {}
+ if context is None:
+ context = {}
+ resource_pool = self.pool.get('resource.resource')
+ for phase in self.browse(cr, uid, ids, context=context):
+ user_ids = map(lambda x:x.resource_id.user_id.id, phase.resource_ids)
+ project = phase.project_id
+ 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[phase.id] = resource_objs
+ return res
+
+ def generate_schedule(self, cr, uid, ids, start_date, calendar_id=False, context=None):
+ """
+ Schedule phase with the start date till all the next phases are completed.
+ @param: start_dsate : start date for the phase
+ @param: calendar_id : working calendar of the project
+ """
+ if context is None:
+ context = {}
+ resource_pool = self.pool.get('resource.resource')
+ uom_pool = self.pool.get('product.uom')
+ phase_resource = False
+ if context is None:
+ context = {}
+ default_uom_id = self._get_default_uom_id(cr, uid)
+ for phase in self.browse(cr, uid, ids, context=context):
+ if not phase.responsible_id:
+ raise osv.except_osv(_('No responsible person assigned !'),_("You must assign a responsible person for phase '%s' !") % (phase.name,))
+
+ phase_resource_obj = resource_pool.generate_resources(cr, uid, [phase.responsible_id.id], calendar_id, context=context)
+ avg_hours = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
+ duration = str(avg_hours) + 'H'
+ # Create a new project for each phase
+ def Project():
+ start = start_date
+ minimum_time_unit = 1
+ resource = phase_resource_obj
+ # If project has working calendar then that
+ # else the default one would be considered
+ if calendar_id:
+ working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
+ vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id))
+
+ def phase():
+ effort = duration
+
+ project = Task.BalancedProject(Project)
+ s_date = project.phase.start.to_datetime()
+ e_date = project.phase.end.to_datetime()
+ # Recalculate date_start and date_end
+ # according to constraints on date start and date end on phase
+ if phase.constraint_date_start and str(s_date) < phase.constraint_date_start:
+ start_date = datetime.strptime(phase.constraint_date_start, '%Y-%m-%d')
+ else:
+ start_date = s_date
+ if phase.constraint_date_end and str(e_date) > phase.constraint_date_end:
+ end_date= datetime.strptime(phase.constraint_date_end, '%Y-%m-%d')
+ date_start = phase.constraint_date_end
+ else:
+ end_date = e_date
+ date_start = end_date
+ # Write the calculated dates back
+ ctx = context.copy()
+ ctx.update({'scheduler': True})
+ self.write(cr, uid, [phase.id], {
+ 'date_start': start_date.strftime('%Y-%m-%d'),
+ 'date_end': end_date.strftime('%Y-%m-%d')
+ }, context=ctx)
+
+ # Recursive call till all the next phases scheduled
+ for phase in phase.next_phase_ids:
+ if phase.state in ['draft', 'open', 'pending']:
+ id_cal = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False
+ self.generate_schedule(cr, uid, [phase.id], date_start, id_cal, context=context)
+ else:
+ continue
+ return True
+
+ def schedule_tasks(self, cr, uid, ids, context=None):
+ """
+ Schedule the tasks according to resource available and priority.
+ """
+ task_pool = self.pool.get('project.task')
+ resource_pool = self.pool.get('resource.resource')
+ if context is None:
+ context = {}
+ resources_list = self.generate_resources(cr, uid, ids, context=context)
+ return_msg = {}
+ for phase in self.browse(cr, uid, ids, context=context):
+ start_date = phase.date_start
+ if not start_date and phase.project_id.date_start:
+ start_date = phase.project_id.date_start
+ if not start_date:
+ start_date = datetime.now().strftime("%Y-%m-%d")
+ resources = resources_list.get(phase.id, [])
+ calendar_id = phase.project_id.resource_calendar_id.id
+ task_ids = map(lambda x : x.id, (filter(lambda x : x.state in ['open', 'draft', 'pending'] , phase.task_ids)))
+ if task_ids:
+ task_pool.generate_schedule(cr, uid, task_ids, resources, calendar_id, start_date, context=context)
+
+ if not task_ids:
+ warning_msg = _("No tasks to compute for Phase '%s'.") % (phase.name)
+ if "warning" not in return_msg:
+ return_msg["warning"] = warning_msg
+ else:
+ return_msg["warning"] = return_msg["warning"] + "\n" + warning_msg
+ return return_msg
project_phase()
class project_resource_allocation(osv.osv):
@@ -266,14 +389,172 @@ 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')
+ if context is None:
+ context = {}
+ 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 the phases.
+ """
+ if context is None:
+ context = {}
+ if type(ids) in (long, int,):
+ ids = [ids]
+ phase_pool = self.pool.get('project.phase')
+ for project in self.browse(cr, uid, ids, context=context):
+ phase_ids = phase_pool.search(cr, uid, [('project_id', '=', project.id),
+ ('state', 'in', ['draft', 'open', 'pending']),
+ ('previous_phase_ids', '=', False)
+ ])
+ start_date = project.date_start
+ if not start_date:
+ start_date = datetime.now().strftime("%Y-%m-%d")
+ start_dt = datetime.strftime((datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d %H:%M")
+ calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
+ phase_pool.generate_schedule(cr, uid, phase_ids, start_dt, calendar_id, context=context)
+ return True
+
+ def schedule_tasks(self, cr, uid, ids, context=None):
+ """
+ Schedule the tasks according to resource available and priority.
+ """
+ if type(ids) in (long, int,):
+ ids = [ids]
+ user_pool = self.pool.get('res.users')
+ task_pool = self.pool.get('project.task')
+ resource_pool = self.pool.get('resource.resource')
+ if context is None:
+ context = {}
+
+ resources_list = self.generate_members(cr, uid, ids, context=context)
+ return_msg = {}
+ for project in self.browse(cr, uid, ids, context=context):
+ start_date = project.date_start
+ if not start_date:
+ start_date = datetime.now().strftime("%Y-%m-%d")
+ resources = resources_list.get(project.id, [])
+ calendar_id = project.resource_calendar_id.id
+ task_ids = task_pool.search(cr, uid, [('project_id', '=', project.id),
+ ('state', 'in', ['draft', 'open', 'pending'])
+ ])
+
+
+ if task_ids:
+ task_pool.generate_schedule(cr, uid, task_ids, resources, calendar_id, start_date, context=context)
+ else:
+ warning_msg = _("No tasks to compute for Project '%s'.") % (project.name)
+ if "warning" not in return_msg:
+ return_msg["warning"] = warning_msg
+ else:
+ return_msg["warning"] = return_msg["warning"] + "\n" + warning_msg
+
+ return return_msg
project()
-class task(osv.osv):
+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'])
+ 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'),
}
+
+ def generate_schedule(self, cr, uid, ids, resources, calendar_id, start_date, context=None):
+ """
+ Schedule the tasks according to resource available and priority.
+ """
+ if not ids:
+ return False
+ if context is None:
+ context = {}
+ user_pool = self.pool.get('res.users')
+ project_pool = self.pool.get('project.project')
+ priority_dict = {'0': 1000, '1': 800, '2': 500, '3': 300, '4': 100}
+ # Create dynamic no of tasks with the resource specified
+ def create_tasks(task_number, eff, priorty=500, obj=False):
+ def task():
+ """
+ task is a dynamic method!
+ """
+ effort = eff
+ if obj:
+ resource = obj
+ priority = priorty
+ task.__doc__ = "TaskNO%d" %task_number
+ task.__name__ = "task%d" %task_number
+ return task
-task()
+ # Create a 'Faces' project with all the tasks and resources
+ def Project():
+ title = "Project"
+ start = datetime.strftime(datetime.strptime(start_date, "%Y-%m-%d"), "%Y-%m-%d %H:%M")
+ try:
+ resource = reduce(operator.or_, resources)
+ except:
+ raise osv.except_osv(_('Error'), _('Should have Resources Allocation or Project Members!'))
+ minimum_time_unit = 1
+ if calendar_id: # If project has working calendar
+ working_days = resource_pool.compute_working_calendar(cr, uid, calendar_id, context=context)
+ vacation = tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context))
+ # Dynamic creation of tasks
+ task_number = 0
+ for openobect_task in self.browse(cr, uid, ids, context=context):
+ hours = str(openobect_task.planned_hours )+ 'H'
+ if openobect_task.priority in priority_dict.keys():
+ priorty = priority_dict[openobect_task.priority]
+ real_resource = False
+ if openobect_task.user_id:
+ for task_resource in resources:
+ if task_resource.__name__ == task_resource:
+ real_resource = task_resource
+ break
+
+ task = create_tasks(task_number, hours, priorty, real_resource)
+ task_number += 1
+
+
+ face_projects = Task.BalancedProject(Project)
+ loop_no = 0
+ # Write back the computed dates
+ for face_project in face_projects:
+ s_date = face_project.start.to_datetime()
+ e_date = face_project.end.to_datetime()
+ if loop_no > 0:
+ ctx = context.copy()
+ ctx.update({'scheduler': True})
+ user_id = user_pool.search(cr, uid, [('name', '=', face_project.booked_resource[0].__name__)])
+ self.write(cr, uid, [ids[loop_no-1]], {
+ 'date_start': s_date.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date_end': e_date.strftime('%Y-%m-%d %H:%M:%S'),
+ 'user_id': user_id[0]
+ }, context=ctx)
+
+ loop_no += 1
+ return True
+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 7718048dc05..30acac99377 100644
--- a/addons/project_long_term/project_long_term_demo.xml
+++ b/addons/project_long_term/project_long_term_demo.xml
@@ -1,741 +1,393 @@
-
-
-
-
-
- en_US
- Demo User1
-
- Mr Demo
-
-
-
-
-
- demo1
- demo1
-
-
-
-
-
- en_US
- Manager1
+
+
+
+
+ Phoung
-
-
-
-
-
- manager1
- manager1
-
+ phu
+ phu
-
-
-
- en_US
- Manager2
+
+ Anthony
-
-
-
-
-
- manager2
- manager2
-
+ al
+ al
-
-
-
- en_US
- user1
+
+ Qwintin
-
-
-
-
-
- user1
- USER1
-
+ qdp
+ qdp
-
-
-
- en_US
- user2
+
+ Francois
-
-
-
-
-
- user2
- user2
-
+ fpi
+ fpi
-
-
-
- en_US
- user_finance
+
+ Julien
+
+ jth
+ jth
+
+
+ Alien
+
+ apr
+ apr
+
+
+ Fabian
-
-
-
-
-
- user_finance
- user_finance
-
+ fbs
+ fbs
-
-
- en_US
- user_design1
-
-
-
-
-
-
- user_design1
- user_design1
-
-
-
-
-
- en_US
- user_design2
-
-
-
-
-
-
- user_design2
- user_design2
-
-
-
-
-
- en_US
- user_developer1
-
-
-
-
-
-
- user_developer1
- user_developer1
-
-
-
-
-
- en_US
- user_developer2
-
-
-
-
-
-
- user_developer2
- user_developer1
-
-
-
-
-
- en_US
- user_tester1
-
-
-
-
-
-
- user_tester1
- user_tester1
-
-
-
-
-
- en_US
- user_tester2
-
-
-
-
-
-
- user_tester2
- user_tester2
-
-
-
-
-
- en_US
- user_analyst
-
-
-
-
-
-
- user_analyst
- user_analyst
-
-
-
-
-
-
-
-
+
+
+ Analyst
-
-
- user
-
-
-
- Programmer
-
-
+
+
+ Project Manager
- user
-
-
-
+
+
+ Technical Leader
+
+
+
+
+
+ Developer
+
+
+
+
+
+ Designer
+
+
+
+
+ Tester
-
-
- user
-
-
-
- Web Designer
-
-
+
+
+ Project Finacial Manager
- user
+
+
-
-
-
-
-
-
-
-
-
+
+ opennormal
- Develop an OpenERP Website.
-
-
-
-
-
- OpenERP Website Development
+ Integrate an Sale and Warehouse Management.
+
+
+ Integrate an Sale and Warehouse Management.
-
+
- Information Gathering and Understanding
+ Collect Requirement and Make SRS
-
-
-
- draft
-
-
-
-
-
+
+ 30
+
+
-
-
- Planning
+
+
+ Design Model
-
-
-
- draft
-
-
-
-
-
+
+ 20
+
+
-
-
- Web Design
+
+
+ Planning and compute Risk analysis, Time chart
-
-
-
- draft
-
-
-
-
-
+
+ 20
+
+
-
-
- Development, Integration and Testing
+
+
+ Development and Integration
-
-
-
- draft
-
-
-
-
-
+
+ 90
+
+
-
-
- Website Deployment
+
+
+ Review and Testing
-
-
-
- draft
-
-
-
-
-
+
+ 30
+
+
+
+
+
+
+ Deployement and Training
+
+
+ 10
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
-
-
+
+
-
- open
-
-
+ 2
-
-
-
-
+ Prepare Requirements Document
-
-
-
-
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
- Define Website Goals, Purposes and Target Audience
-
-
-
-
+
+ Make SRS
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
- Use Cases
-
-
-
-
+
+ Design Use Cases
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
+ Budget Planning
-
-
-
-
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
+ Quality Planning
-
-
-
-
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
+ Risk Management Planning
-
-
-
-
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
+ Create Project Schedules
-
-
-
-
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
+ Dataflow Design
-
-
-
-
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
+ User Interface Design
-
-
-
-
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
- Task Level WBS
-
-
-
-
+
+ Develop Module in Sale Management
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
- Modular Programming
-
-
-
-
+
+ Develop module in Warehouse
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
+ Integrate Modules
-
-
-
-
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
+ Unit Testing
-
-
-
-
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
+ Regression Test
-
-
-
-
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
+ Documentation
-
-
-
-
+
-
+
-
- open
-
-
+ 2
-
-
-
-
+ Performance Tuning
-
-
-
-
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
- Deploy Website
-
-
-
-
+
+ Deploy and Review on Customer System
+
-
-
+
-
- open
-
-
+ 2
-
-
-
-
- Presentation
-
-
-
-
+
+ Training and Presentation
+
+
+
+
+
+
diff --git a/addons/project_long_term/test/schedule_phase_tasks.yml b/addons/project_long_term/test/schedule_phase_tasks.yml
index 323d9cbc8f2..150d80af822 100644
--- a/addons/project_long_term/test/schedule_phase_tasks.yml
+++ b/addons/project_long_term/test/schedule_phase_tasks.yml
@@ -2,32 +2,31 @@
Create project 'Develop an outlook-openerp synchronization plugin'
-
!record {model: project.project, id: project_project_project0}:
- company_id: base.main_company
- name: Project123
+ name: Develop an outlook-openerp synchronization plugin
members:
- - project_long_term.res_users_manager0
- - project_long_term.res_users_user0
- - project_long_term.res_users_userfinance0
- - project_long_term.res_users_userdesign0
- - project_long_term.res_users_userdeveloper0
- - project_long_term.res_users_usertester0
- - project_long_term.res_users_useranalyst0
+ - project_long_term.res_users_project_manager
+ - project_long_term.res_users_technical_leader
+ - project_long_term.res_users_developer
+ - project_long_term.res_users_designer
+ - project_long_term.res_users_tester
+ - project_long_term.res_users_analyst
+ - project_long_term.res_users_finacial_manager
-
Create phase 'Develop GUI' in Outlook
-
!record {model: project.phase, id: project_phase_phase0}:
date_start: '2010-06-02'
duration: 200.0
- name: phase123
+ name: Develop GUI in Outlook
product_uom: product.uom_day
project_id: project_project_project0
state: draft
resource_ids:
- - resource_id: project_long_term.resource_resource_analyst0
+ - resource_id: project_long_term.resource_analyst
useability: 80.0
- - resource_id: project_long_term.resource_resource_programmer0
+ - resource_id: project_long_term.resource_developer
useability: 30.0
- - resource_id: project_long_term.resource_resource_webdesigner0
+ - resource_id: project_long_term.resource_designer
useability: 30.0
@@ -35,66 +34,60 @@
Create the phase task 'Develop GUI for Server Configuration'
-
!record {model: project.task, id: project_task_t0}:
- name: t1
+ name: Develop GUI for Server Configuration
planned_hours: 20.0
remaining_hours: 20.0
state: draft
phase_id: project_phase_phase0
project_id: project_project_project0
- user_id: project_long_term.res_users_userdeveloper0
+ user_id: project_long_term.res_users_developer
-
Create the phase task 'Develop GUI for Modules Configuration'
-
!record {model: project.task, id: project_task_t1}:
- name: t2
+ name: Develop GUI for Modules Configuration
planned_hours: 25.0
remaining_hours: 25.0
state: draft
phase_id: project_phase_phase0
project_id: project_project_project0
- user_id: project_long_term.res_users_userdeveloper0
+ user_id: project_long_term.res_users_developer
-
Create the phase task 'Develop GUI for OpenERP Synchronisation'
-
!record {model: project.task, id: project_task_t2}:
- name: t3
+ name: Develop GUI for OpenERP Synchronisation
planned_hours: 30.0
remaining_hours: 30.0
state: draft
phase_id: project_phase_phase0
project_id: project_project_project0
- user_id: project_long_term.res_users_userdeveloper0
+ user_id: project_long_term.res_users_developer
-
- Create the phase task 'Develop required Menus'
+ Create the phase task 'Design required GUI/Menus'
-
!record {model: project.task, id: project_task_3}:
- name: t4
+ name: Design required GUI/Menus
planned_hours: 25.0
remaining_hours: 25.0
state: draft
phase_id: project_phase_phase0
project_id: project_project_project0
- user_id: project_long_term.res_users_userdesign0
+ user_id: project_long_term.res_users_designer
-
Schedule phase tasks
-
- !python {model: project.schedule.tasks}: |
- res = self.compute_date(cr, uid, {"lang": "en_US", "tz": False,
- "active_id": ref("project_phase_phase0"),
- # rest may be obsolete:
- # "active_model": "project.phase", "active_ids": [ref("project_phase_phase0")],
- # "record_id": 138, "project_id": ref("project_project_project0"),
- })
- print res
+ !python {model: project.phase}: |
+ res = self.schedule_tasks(cr, uid, [ref("project_phase_phase0")])
-
Check if tasks scheduled, check that either of task's start_date, end_date and user_id is not null
-
!python {model: project.phase}: |
- phase=self.browse(cr, uid, [ref("project_phase_phase0")])[0]
+ phase = self.browse(cr, uid, [ref("project_phase_phase0")])[0]
for task in phase.task_ids:
if (not task.user_id) or (not task.date_start) or (not task.date_end):
raise AssertionError("Phase Tasks not scheduled: %d uid=%r start=%r end=%r" % \
- (task.id, task.user_id, task.date_start, task.date_end))
\ No newline at end of file
+ (task.id, task.user_id, task.date_start, task.date_end))
diff --git a/addons/project_long_term/test/schedule_project_phases.yml b/addons/project_long_term/test/schedule_project_phases.yml
index 66bca5ff97d..e06feaaa4ca 100644
--- a/addons/project_long_term/test/schedule_project_phases.yml
+++ b/addons/project_long_term/test/schedule_project_phases.yml
@@ -2,7 +2,6 @@
Create a project 'World Bank's Project'
-
!record {model: project.project, id: project_project_worldbanksproject0}:
- company_id: base.main_company
name: "World Bank's Project"
priority: 4
@@ -15,7 +14,6 @@
name: "Defining Client's Basic Idea of Project"
product_uom: product.uom_day
project_id: project_project_worldbanksproject0
- state: draft
-
@@ -27,7 +25,6 @@
name: Establishing Project Feasibility
product_uom: product.uom_day
project_id: project_project_worldbanksproject0
- state: draft
-
Create project phase 'Preparation of Engineering Designs'
@@ -38,7 +35,6 @@
name: Preparation of Engineering Designs
product_uom: product.uom_hour
project_id: project_project_worldbanksproject0
- state: draft
-
@@ -50,7 +46,6 @@
name: Procurement of Works and Goods
product_uom: product.uom_hour
project_id: project_project_worldbanksproject0
- state: draft
-
@@ -62,7 +57,6 @@
name: Project Construction
product_uom: product.uom_hour
project_id: project_project_worldbanksproject0
- state: draft
-
@@ -74,7 +68,6 @@
name: Project Completion
product_uom: product.uom_hour
project_id: project_project_worldbanksproject0
- state: draft
-
Set previous phase for phase 'Establishing Project Feasibility'
@@ -125,11 +118,8 @@
-
Schedule project phases
-
- !python {model: project.compute.phases}: |
- self.check_selection(cr, uid, [ref("project_compute_phases_0")], {"lang": "en_US",
- "project_id": False, "tz": False, "active_model": "ir.ui.menu", "department_id":
- False, "section_id": False, "record_id": 279, "active_ids": [ref("project_long_term.menu_compute_phase")],
- "active_id": ref("project_long_term.menu_compute_phase"), })
+ !python {model: project.project}: |
+ self.schedule_phases(cr, uid, ref("project_project_worldbanksproject0"))
-
Check if phases scheduled, check that either of phase's start_date, end_date and user_id is not null
@@ -140,4 +130,4 @@
if (not phase.responsible_id) or (not phase.date_start) or (not phase.date_end):
raise AssertionError("Tasks not scheduled")
-
\ No newline at end of file
+
diff --git a/addons/project_long_term/test/schedule_project_tasks.yml b/addons/project_long_term/test/schedule_project_tasks.yml
index b1cd70487f7..dfc4cf1fc5e 100644
--- a/addons/project_long_term/test/schedule_project_tasks.yml
+++ b/addons/project_long_term/test/schedule_project_tasks.yml
@@ -2,103 +2,93 @@
Create project 'Develop a thunderbird-openerp synchronization plugin'
-
!record {model: project.project, id: project_project_project1}:
- company_id: base.main_company
- name: Project2
+ name: Develop a thunderbird-openerp synchronization plugin
members:
- - project_long_term.res_users_manager0
- - project_long_term.res_users_user0
- - project_long_term.res_users_userfinance0
- - project_long_term.res_users_userdesign0
- - project_long_term.res_users_userdeveloper0
- - project_long_term.res_users_usertester0
- - project_long_term.res_users_useranalyst0
+ - project_long_term.res_users_project_manager
+ - project_long_term.res_users_technical_leader
+ - project_long_term.res_users_developer
+ - project_long_term.res_users_designer
+ - project_long_term.res_users_tester
+ - project_long_term.res_users_analyst
+ - project_long_term.res_users_finacial_manager
-
- Create phase 'Develop GUI' in Thunderbird
+ Create phase 'Develop GUI' in thunderbird
-
!record {model: project.phase, id: project_phase_phase1}:
date_start: '2010-06-02'
- duration: 100.0
- name: phase22
+ duration: 200.0
+ name: Develop GUI in thunderbird
product_uom: product.uom_day
project_id: project_project_project1
state: draft
resource_ids:
- - resource_id: project_long_term.resource_resource_analyst0
+ - resource_id: project_long_term.resource_analyst
useability: 80.0
- - resource_id: project_long_term.resource_resource_programmer0
+ - resource_id: project_long_term.resource_developer
useability: 30.0
- - resource_id: project_long_term.resource_resource_webdesigner0
+ - resource_id: project_long_term.resource_designer
useability: 30.0
-
Create the phase task 'Develop GUI for Server Configuration'
-
- !record {model: project.task, id: project_task_t00}:
- name: t1
+ !record {model: project.task, id: project_task_t10}:
+ name: Develop GUI for Server Configuration
planned_hours: 20.0
remaining_hours: 20.0
state: draft
phase_id: project_phase_phase1
project_id: project_project_project1
- user_id: project_long_term.res_users_userdeveloper0
+ user_id: project_long_term.res_users_developer
-
Create the phase task 'Develop GUI for Modules Configuration'
-
!record {model: project.task, id: project_task_t11}:
- name: t2
+ name: Develop GUI for Modules Configuration
planned_hours: 25.0
remaining_hours: 25.0
state: draft
phase_id: project_phase_phase1
project_id: project_project_project1
- user_id: project_long_term.res_users_userdeveloper0
+ user_id: project_long_term.res_users_developer
-
Create the phase task 'Develop GUI for OpenERP Synchronisation'
-
- !record {model: project.task, id: project_task_t22}:
- name: t3
+ !record {model: project.task, id: project_task_t12}:
+ name: Develop GUI for OpenERP Synchronisation
planned_hours: 30.0
remaining_hours: 30.0
state: draft
phase_id: project_phase_phase1
project_id: project_project_project1
-
-
+ user_id: project_long_term.res_users_developer
-
- Create the phase task 'Develop required Menus'
+ Create the phase task 'Design required GUI/Menus'
-
- !record {model: project.task, id: project_task_t33}:
- name: t4
+ !record {model: project.task, id: project_task_13}:
+ name: Design required GUI/Menus
planned_hours: 25.0
remaining_hours: 25.0
state: draft
phase_id: project_phase_phase1
project_id: project_project_project1
-
--
- Get the project to schedule the tasks
--
- !record {model: project.compute.tasks, id: project_compute_tasks_0}:
- project_id: project_project_project1
+ user_id: project_long_term.res_users_designer
-
Schedule tasks
-
- !python {model: project.compute.tasks}: |
- self.compute_date(cr, uid, [ref("project_compute_tasks_0")], {"lang": "en_US", "tz": False,
- "active_id": ref("project_long_term.menu_compute_tasks"),
- "active_model": "ir.ui.menu", "active_ids": [ref("project_long_term.menu_compute_tasks")],
- })
+ !python {model: project.project}: |
+ self.schedule_tasks(cr, uid, ref("project_project_project1"))
-
Check if tasks scheduled, check that either of task's start_date, end_date and user_id is not null
-
!python {model: project.project}: |
- prj=self.browse(cr, uid, [ref("project_project_project1")])[0]
+ prj = self.browse(cr, uid, [ref("project_project_project1")])[0]
for task in prj.tasks:
if (not task.user_id) or (not task.date_start) or (not task.date_end):
raise AssertionError("Project Tasks not scheduled")
diff --git a/addons/project_long_term/wizard/__init__.py b/addons/project_long_term/wizard/__init__.py
index 5056cad3b77..c1e6d838111 100644
--- a/addons/project_long_term/wizard/__init__.py
+++ b/addons/project_long_term/wizard/__init__.py
@@ -22,6 +22,5 @@
import project_compute_phases
import project_compute_tasks
import project_schedule_tasks
-import working_calendar
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project_long_term/wizard/project_compute_phases.py b/addons/project_long_term/wizard/project_compute_phases.py
index 75631fb3d18..c43dea56e91 100644
--- a/addons/project_long_term/wizard/project_compute_phases.py
+++ b/addons/project_long_term/wizard/project_compute_phases.py
@@ -18,18 +18,11 @@
# along with this program. If not, see .
#
##############################################################################
-
import datetime
-from resource.faces import *
-from new import classobj
-
from tools.translate import _
from osv import fields, osv
-import working_calendar as wkcal
-
class project_compute_phases(osv.osv_memory):
-
_name = 'project.compute.phases'
_description = 'Project Compute Phases'
_columns = {
@@ -48,118 +41,24 @@ class project_compute_phases(osv.osv_memory):
data_select = self.read(cr, uid, ids, ['target_project'])[0]
return self.compute_date(cr, uid, ids, context=context)
- def _phase_schedule(self, cr, uid, phase, start_date, calendar_id=False, context=None):
- if context is None:
- context = {}
- """Schedule phase with the start date till all the next phases are completed.
-
- Arguements: start_dsate -- start date for the phase
- calendar_id -- working calendar of the project
-
- """
- phase_obj = self.pool.get('project.phase')
- resource_obj = self.pool.get('resource.resource')
- uom_obj = self.pool.get('product.uom')
- phase_resource_obj = False
- if context is None:
- context = {}
- if phase:
- leaves = []
- time_efficiency = 1.0
- resource_id = resource_obj.search(cr, uid, [('user_id', '=', phase.responsible_id.id)])
-
- if resource_id:
- # Create a new resource object with
- # all the attributes of the Resource Class
- resource = resource_obj.browse(cr, uid, resource_id, context=context)[0]
- time_efficiency = resource.time_efficiency
- leaves = wkcal.compute_leaves(cr, uid, calendar_id , resource.id, resource.calendar_id.id)
- if not phase.responsible_id:
- raise osv.except_osv(_('No responsible person assigned !'),_("You must assign a responsible person for phase '%s' !") % (phase.name,))
- phase_resource_obj = classobj((phase.responsible_id.name.encode('utf8')), (Resource,),{
- '__doc__': phase.responsible_id.name,
- '__name__': phase.responsible_id.name,
- 'vacation': tuple(leaves),
- 'efficiency': time_efficiency
- })
- default_uom_id = phase_obj._get_default_uom_id(cr, uid)
- avg_hours = uom_obj._compute_qty(cr, uid, phase.product_uom.id, phase.duration, default_uom_id)
- duration = str(avg_hours) + 'H'
- # Create a new project for each phase
- def Project():
- start = start_date
- minimum_time_unit = 1
- resource = phase_resource_obj
- # If project has working calendar then that
- # else the default one would be considered
- if calendar_id:
- working_days = wkcal.compute_working_calendar(cr, uid, calendar_id, context=context)
- vacation = tuple(wkcal.compute_leaves(cr, uid, calendar_id))
-
- def phase():
- effort = duration
-
- project = BalancedProject(Project)
- s_date = project.phase.start.to_datetime()
- e_date = project.phase.end.to_datetime()
- # Recalculate date_start and date_end
- # according to constraints on date start and date end on phase
- if phase.constraint_date_start and str(s_date) < phase.constraint_date_start:
- start_date = datetime.datetime.strptime(phase.constraint_date_start, '%Y-%m-%d')
- else:
- start_date = s_date
- if phase.constraint_date_end and str(e_date) > phase.constraint_date_end:
- end_date= datetime.datetime.strptime(phase.constraint_date_end, '%Y-%m-%d')
- date_start = phase.constraint_date_end
- else:
- end_date = e_date
- date_start = end_date
- # Write the calculated dates back
- ctx = context.copy()
- ctx.update({'scheduler': True})
- phase_obj.write(cr, uid, [phase.id], {
- 'date_start': start_date.strftime('%Y-%m-%d'),
- 'date_end': end_date.strftime('%Y-%m-%d')
- }, context=ctx)
-
- # Recursive call till all the next phases scheduled
- for phase in phase.next_phase_ids:
- if phase.state in ['draft', 'open', 'pending']:
- id_cal = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False
- self._phase_schedule(cr, uid, phase, date_start, id_cal, context=context)
- else:
- continue
-
+
def compute_date(self, cr, uid, ids, context=None):
"""
Compute the phases for scheduling.
"""
-
if context is None:
context = {}
- phase_obj = self.pool.get('project.phase')
+ project_pool = self.pool.get('project.project')
data = self.read(cr, uid, ids, [], context=context)[0]
if not data['project_id'] and data['target_project'] == 'one':
raise osv.except_osv(_('Error!'), _('Please Specify Project to be schedule'))
+
if data['project_id']: # If project mentioned find its phases
- project_id = data['project_id']
- phase_ids = phase_obj.search(cr, uid, [('project_id', '=', project_id),
- ('state', 'in', ['draft', 'open', 'pending']),
- ('previous_phase_ids', '=', False)
- ])
+ project_ids = [data['project_id']]
else: # Else take all the draft,open,pending states phases
- phase_ids = phase_obj.search(cr, uid,[('state', 'in', ['draft', 'open', 'pending']),
- ('previous_phase_ids', '=', False)
- ], context=context)
- phase_ids.sort()
- phases = phase_obj.browse(cr, uid, phase_ids, context=context)
- for phase in phases:
- start_date = phase.project_id.date_start
- if not phase.project_id.date_start:
- start_date = datetime.datetime.now().strftime("%Y-%m-%d")
- start_dt = datetime.datetime.strftime((datetime.datetime.strptime(start_date, "%Y-%m-%d")), "%Y-%m-%d %H:%M")
- calendar_id = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False
- self._phase_schedule(cr, uid, phase, start_dt, calendar_id, context=context)
+ project_ids = project_pool.search(cr, uid, [], context=context)
+
+ project_pool.schedule_phases(cr, uid, project_ids, context=context)
return self._open_phases_list(cr, uid, data, context=context)
def _open_phases_list(self, cr, uid, data, context=None):
@@ -173,10 +72,7 @@ class project_compute_phases(osv.osv_memory):
result = mod_obj._get_id(cr, uid, 'project_long_term', 'act_project_phase_list')
id = mod_obj.read(cr, uid, [result], ['res_id'])[0]['res_id']
result = act_obj.read(cr, uid, [id], context=context)[0]
- result['domain'] = [('state', 'not in', ['cancelled','done'])]
- if data['project_id']:
- result['domain'] = [('project_id', '=', data['project_id']),
- ('state', 'not in', ['cancelled','done'])]
+ result['context'] = {"search_default_project_id":data['project_id'], "search_default_responsible_id":uid, "search_default_current": 1}
return result
project_compute_phases()
diff --git a/addons/project_long_term/wizard/project_compute_tasks.py b/addons/project_long_term/wizard/project_compute_tasks.py
index 8e98f26b353..cb74649502f 100644
--- a/addons/project_long_term/wizard/project_compute_tasks.py
+++ b/addons/project_long_term/wizard/project_compute_tasks.py
@@ -20,14 +20,10 @@
##############################################################################
import datetime
-from resource.faces import *
-from new import classobj
-import operator
from tools.translate import _
from osv import osv, fields
-import working_calendar as wkcal
class project_compute_tasks(osv.osv_memory):
_name = 'project.compute.tasks'
@@ -40,108 +36,32 @@ class project_compute_tasks(osv.osv_memory):
"""
Schedule the tasks according to resource available and priority.
"""
- project_obj = self.pool.get('project.project')
+ project_pool = self.pool.get('project.project')
task_pool = self.pool.get('project.task')
- resource_obj = self.pool.get('resource.resource')
- user_obj = self.pool.get('res.users')
-
if context is None:
context = {}
-
+ context['compute_by'] = 'project'
data = self.read(cr, uid, ids, [])[0]
project_id = data['project_id']
- project = project_obj.browse(cr, uid, project_id, context=context)
- task_ids = task_pool.search(cr, uid, [('project_id', '=', project_id),
- ('state', 'in', ['draft', 'open', 'pending'])
- ])
- if task_ids:
- task_ids.sort()
- tasks = task_pool.browse(cr, uid, task_ids, context=context)
- calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
- start_date = project.date_start
- if not project.date_start:
- start_date = datetime.datetime.now().strftime("%Y-%m-%d")
- date_start = datetime.datetime.strftime(datetime.datetime.strptime(start_date, "%Y-%m-%d"), "%Y-%m-%d %H:%M")
- # Create Resource Class objects which are the Project Members
- resources = []
- for user in project.members:
- leaves = []
- time_efficiency = 1.0
- resource_id = resource_obj.search(cr, uid, [('user_id', '=', user.id)], context=context)
- if resource_id:
-# resource = resource_obj.browse(cr, uid, resource_id, context=context)[0]
- resource = resource_obj.read(cr, uid, resource_id, ['calendar_id', 'time_efficiency'], context=context)[0]
- if resource.get('calendar_id', False):
- leaves = wkcal.compute_leaves(cr, uid, calendar_id , resource_id[0], resource['calendar_id'] and resource['calendar_id'][0] or False)
- time_efficiency = resource.get('time_efficiency')
- resources.append(classobj((user.name.encode('utf8')), (Resource,), {
- '__doc__': user.name,
- '__name__': user.name,
- 'vacation': tuple(leaves),
- 'efficiency': time_efficiency
- }))
- priority_dict = {'0': 1000, '1': 800, '2': 500, '3': 300,'4': 100}
- # Create dynamic no of tasks with the resource specified
- def create_tasks(j, eff, priorty=500, obj=None):
- def task():
- """
- task is a dynamic method!
- """
- effort = eff
- if obj:
- resource = obj
- priority = priorty
- task.__doc__ = "TaskNO%d" %j
- task.__name__ = "task%d" %j
- return task
+ project_pool.schedule_tasks(cr, uid, [project_id], context=context)
+ return self._open_task_list(cr, uid, data, context=context)
- # Create a 'Faces' project with all the tasks and resources
- def Project():
- title = project.name
- start = date_start
- try:
- resource = reduce(operator.or_, resources)
- except:
- raise osv.except_osv(_('Error'), _('Project must have members assigned !'))
- minimum_time_unit = 1
- if calendar_id: # If project has calendar
- working_days = wkcal.compute_working_calendar(cr, uid, calendar_id)
- vacation = tuple(wkcal.compute_leaves(cr, uid, calendar_id))
- # Dynamic Creation of tasks
- i = 0
- for each_task in tasks:
- hours = str(each_task.planned_hours )+ 'H'
- if each_task.priority in priority_dict.keys():
- priorty = priority_dict[each_task.priority]
- rcre = False
- if each_task.user_id:
- for resrce in resources:
- if resrce.__name__ == each_task.user_id.name: # check me!!
- rcre = resrce
- break
- task = create_tasks(i, hours, priorty, rcre)
- i += 1
-
- project = BalancedProject(Project)
- loop_no = 0
- # Write back the computed dates
- for t in project:
- s_date = t.start.to_datetime()
- e_date = t.end.to_datetime()
- if loop_no == 0:
- project_obj.write(cr, uid, [project_id], {'date': e_date}, context=context)
- else:
- ctx = context.copy()
- ctx.update({'scheduler': True})
- user_id = user_obj.search(cr, uid, [('name', '=', t.booked_resource[0].__name__)])
- task_pool.write(cr, uid, [tasks[loop_no-1].id], {
- 'date_start': s_date.strftime('%Y-%m-%d %H:%M:%S'),
- 'date_end': e_date.strftime('%Y-%m-%d %H:%M:%S'),
- 'user_id': user_id[0]
- }, context=ctx)
-
- loop_no +=1
- return {}
+ def _open_task_list(self, cr, uid, data, context=None):
+ """
+ Return the scheduled task list.
+ """
+ if context is None:
+ context = {}
+ mod_obj = self.pool.get('ir.model.data')
+ act_obj = self.pool.get('ir.actions.act_window')
+ result = mod_obj._get_id(cr, uid, 'project', 'action_view_task')
+ id = mod_obj.read(cr, uid, [result], ['res_id'])[0]['res_id']
+ result = {}
+ if not id:
+ return result
+ result = act_obj.read(cr, uid, [id], context=context)[0]
+ result['context'] = {"search_default_project_id":data['project_id'], "search_default_user_id":uid, "search_default_current": 1}
+ return result
project_compute_tasks()
diff --git a/addons/project_long_term/wizard/project_schedule_tasks.py b/addons/project_long_term/wizard/project_schedule_tasks.py
index c64e800c9c6..0d9e9f381f1 100644
--- a/addons/project_long_term/wizard/project_schedule_tasks.py
+++ b/addons/project_long_term/wizard/project_schedule_tasks.py
@@ -20,17 +20,9 @@
##############################################################################
import datetime
-from resource.faces import *
-from new import classobj
-import operator
-
from tools.translate import _
-from osv import osv, fields
-
-import working_calendar as wkcal
-
+from osv import fields, osv
class project_schedule_task(osv.osv_memory):
-
_name = "project.schedule.tasks"
_description = 'project.schedule.tasks'
_columns = {
@@ -46,126 +38,17 @@ class project_schedule_task(osv.osv_memory):
res = super(project_schedule_task, self).default_get(cr, uid, fields_list, context)
self.compute_date(cr, uid, context=context)
return res
-
- def create_resources(self, cr, uid, phase, context=None):
- """
- Return a list of Resource Class objects for the resources allocated to the phase.
- """
- resource_objs = []
-
- if context is None:
- context = {}
-
- for resource in phase.resource_ids:
- res = resource.resource_id
- leaves = []
- resource_eff = res.time_efficiency
- resource_cal = res.calendar_id.id
- if resource_cal:
- cal_id = phase.project_id.resource_calendar_id and phase.project_id.resource_calendar_id.id or False
- leaves = wkcal.compute_leaves(cr, uid, cal_id, res.id, resource_cal, context=context)
- resource_objs.append(classobj(res.user_id.name.encode('utf8'), (Resource,),{
- '__doc__': res.user_id.name,
- '__name__': res.user_id.name,
- 'vacation': tuple(leaves),
- 'efficiency': resource_eff,
- }))
- return resource_objs
-
+
def compute_date(self, cr, uid, context=None):
"""
Schedule the tasks according to resource available and priority.
"""
-
- phase_obj = self.pool.get('project.phase')
- task_obj = self.pool.get('project.task')
- user_obj = self.pool.get('res.users')
-
+ phase_pool = self.pool.get('project.phase')
if context is None:
- # It makes no sense to continue on empty context
- return { 'warning': _("You must select some project phase to compute on")}
-
+ context = {}
if not 'active_id' in context:
return {}
- phase = phase_obj.browse(cr, uid, context['active_id'], context=context)
- task_ids = map(lambda x : x.id, (filter(lambda x : x.state in ['open', 'draft', 'pending'] , phase.task_ids)))
- if task_ids:
- task_ids.sort()
- tasks = task_obj.browse(cr, uid, task_ids, context=context)
- start_date = str(phase.date_start)
- if not phase.date_start:
- if not phase.project_id.date_start:
- start_date = datetime.datetime.now().strftime("%Y-%m-%d")
- else:
- start_date = phase.project_id.date_start
- date_start = datetime.datetime.strftime(datetime.datetime.strptime(start_date, "%Y-%m-%d"), "%Y-%m-%d %H:%M")
- calendar_id = phase.project_id.resource_calendar_id.id
- resources = self.create_resources(cr, uid, phase)
- priority_dict = {'0': 1000, '1': 800, '2': 500, '3': 300, '4': 100}
- # Create dynamic no of tasks with the resource specified
- def create_tasks(j, eff, priorty=500, obj=False):
- def task():
- """
- task is a dynamic method!
- """
- effort = eff
- if obj:
- resource = obj
- priority = priorty
- task.__doc__ = "TaskNO%d" %j
- task.__name__ = "task%d" %j
- return task
-
- # Create a 'Faces' project with all the tasks and resources
- def Project():
- title = "Project"
- start = date_start
- try:
- resource = reduce(operator.or_, resources)
- except:
- raise osv.except_osv(_('Error'), _('Phase must have resources assigned !'))
- minimum_time_unit = 1
- if calendar_id: # If project has working calendar
- working_days = wkcal.compute_working_calendar(cr, uid, calendar_id)
- vacation = tuple(wkcal.compute_leaves(cr, uid, calendar_id))
- # Dynamic creation of tasks
- i = 0
- for each_task in tasks:
- hours = str(each_task.planned_hours )+ 'H'
- if each_task.priority in priority_dict.keys():
- priorty = priority_dict[each_task.priority]
- resc = False
- if each_task.user_id:
- for resrce in resources:
- if resrce.__name__ == each_task.user_id.name:
- resc = resrce
- break
-
- task = create_tasks(i, hours, priorty, resc)
- i += 1
-
- project = BalancedProject(Project)
-
- loop_no = 0
- # Write back the computed dates
- for t in project:
- s_date = t.start.to_datetime()
- e_date = t.end.to_datetime()
- if loop_no > 0:
- ctx = context.copy()
- ctx.update({'scheduler': True})
- user_id = user_obj.search(cr, uid, [('name', '=', t.booked_resource[0].__name__)])
- task_obj.write(cr, uid, [tasks[loop_no-1].id], {
- 'date_start': s_date.strftime('%Y-%m-%d %H:%M:%S'),
- 'date_end': e_date.strftime('%Y-%m-%d %H:%M:%S'),
- 'user_id': user_id[0]
- }, context=ctx)
-
- loop_no += 1
- else:
- return {"warning": _("No tasks to compute for this phase") }
- return {}
-
+ return phase_pool.schedule_tasks(cr, uid, [context['active_id']], context=context)
project_schedule_task()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project_long_term/wizard/working_calendar.py b/addons/project_long_term/wizard/working_calendar.py
deleted file mode 100644
index 139875b6236..00000000000
--- a/addons/project_long_term/wizard/working_calendar.py
+++ /dev/null
@@ -1,124 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-import datetime
-
-import pooler
-
-def convert_timeformat(cr, uid, time_string, context=None):
- if context is None:
- context = {}
- """ Convert input time string: 8.5 to output time string 8:30."""
-
- split_list = str(time_string).split('.')
- hour_part = split_list[0]
- mins_part = split_list[1]
- round_mins = int(round(float(mins_part) * 60,-2))
- converted_string = hour_part + ':' + str(round_mins)[0:2]
- return converted_string
-
-def compute_leaves(cr, uid, calendar_id, resource_id=False, resource_calendar=False, context=None):
-
- if context is None:
- context = {}
-
- """Compute the leaves from the working calendar of the resource.
-
- Arguements: calendar_id -- working calendar of the project
- resource_id -- resource working on phase/task
- resource_calendar -- working calendar of the resource
-
- """
-
- pool = pooler.get_pool(cr.dbname)
- resource_calendar_leaves_obj = pool.get('resource.calendar.leaves')
- leave_list = []
- if resource_id:
- leave_ids = resource_calendar_leaves_obj.search(cr, uid, ['|', ('calendar_id', '=', calendar_id),
- ('calendar_id', '=', resource_calendar),
- ('resource_id', '=', resource_id)
- ], context=context)
- else:
- leave_ids = resource_calendar_leaves_obj.search(cr, uid, [('calendar_id', '=', calendar_id),
- ('resource_id', '=', False)
- ], context=context)
- leaves = resource_calendar_leaves_obj.read(cr, uid, leave_ids, ['date_from', 'date_to'], context=context)
- for i in range(len(leaves)):
- dt_start = datetime.datetime.strptime(leaves[i]['date_from'], '%Y-%m-%d %H:%M:%S')
- dt_end = datetime.datetime.strptime(leaves[i]['date_to'], '%Y-%m-%d %H:%M:%S')
- no = dt_end - dt_start
- [leave_list.append((dt_start + datetime.timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))]
- leave_list.sort()
- return leave_list
-
-def compute_working_calendar(cr, uid, calendar_id, context=None):
-
- if context is None:
- context = {}
-
- """Change the format of working calendar from 'Openerp' format to bring it into 'Faces' format.
-
- Arguement: calendar_id -- working calendar of the project
-
- """
-
- pool = pooler.get_pool(cr.dbname)
- resource_attendance_obj = pool.get('resource.calendar.attendance')
- time_range = "8:00-8:00"
- non_working = ""
- week_days = {"0": "mon", "1": "tue", "2": "wed","3": "thu", "4": "fri", "5": "sat", "6": "sun"}
- wk_days = {}
- wk_time = {}
- wktime_list = []
- wktime_cal = []
- week_ids = resource_attendance_obj.search(cr, uid, [('calendar_id', '=', calendar_id)], context=context)
- weeks = resource_attendance_obj.read(cr, uid, week_ids, ['dayofweek', 'hour_from', 'hour_to'], context=context)
- # Convert time formats into appropriate format required
- # and create a list like [('mon', '8:00-12:00'), ('mon', '13:00-18:00')]
- for week in weeks:
- res_str = ""
- if week_days.has_key(week['dayofweek']):
- day = week_days[week['dayofweek']]
- wk_days[week['dayofweek']] = week_days[week['dayofweek']]
- hour_from_str = convert_timeformat(cr, uid, week['hour_from'])
- hour_to_str = convert_timeformat(cr, uid, week['hour_to'])
- res_str = hour_from_str + '-' + hour_to_str
- wktime_list.append((day, res_str))
- # Convert into format like [('mon', '8:00-12:00', '13:00-18:00')]
- for item in wktime_list:
- if wk_time.has_key(item[0]):
- wk_time[item[0]].append(item[1])
- else:
- wk_time[item[0]] = [item[0]]
- wk_time[item[0]].append(item[1])
- for k,v in wk_time.items():
- wktime_cal.append(tuple(v))
- # Add for the non-working days like: [('sat, sun', '8:00-8:00')]
- for k, v in wk_days.items():
- if week_days.has_key(k):
- week_days.pop(k)
- for v in week_days.itervalues():
- non_working += v + ','
- if non_working:
- wktime_cal.append((non_working[:-1], time_range))
- return wktime_cal
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project_scrum/project_scrum.py b/addons/project_scrum/project_scrum.py
index 08c55837165..e3e48f70b86 100644
--- a/addons/project_scrum/project_scrum.py
+++ b/addons/project_scrum/project_scrum.py
@@ -320,7 +320,7 @@ class project_scrum_meeting(osv.osv):
'question_blocks': fields.text('Blocks encountered'),
'question_backlog': fields.text('Backlog Accurate'),
'task_ids': fields.many2many('project.task', 'meeting_task_rel', 'metting_id', 'task_id', 'Tasks'),
- 'responsible_id': fields.related('sprint_id', 'scrum_master_id', type='many2one', relation='res.users', string='Responsible'),
+ 'user_id': fields.related('sprint_id', 'scrum_master_id', type='many2one', relation='res.users', string='Responsible', readonly=True),
}
#
# TODO: Find the right sprint thanks to users and date
diff --git a/addons/project_scrum/project_scrum_view.xml b/addons/project_scrum/project_scrum_view.xml
index 7b33658f065..fcf7cae08f5 100644
--- a/addons/project_scrum/project_scrum_view.xml
+++ b/addons/project_scrum/project_scrum_view.xml
@@ -245,7 +245,39 @@
-
+
+
+
+
+
+
+
@@ -319,10 +351,11 @@
project.scrum.meetingtree
-
+
+
@@ -331,11 +364,11 @@
project.scrum.meetingform
-