From b03e5f9034430dde015ad3511f6e4acff4b61936 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 22 Apr 2011 10:54:46 +0200 Subject: [PATCH 01/11] [IMP] Add a cron for the analytic accounts, this cron will send an email to the responsible of the pending accounts bzr revid: stephane@openerp.com-20110422085446-r744ot300i6qwksv --- addons/account_analytic_analysis/__init__.py | 1 + .../account_analytic_analysis/__openerp__.py | 1 + .../account_analytic_analysis.py | 20 ++++- .../account_analytic_analysis_cron.xml | 15 ++++ .../account_analytic_analysis_menu.xml | 58 ++++++++++++++ .../cron_account_analytic_account.py | 79 +++++++++++++++++++ 6 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 addons/account_analytic_analysis/account_analytic_analysis_cron.xml create mode 100644 addons/account_analytic_analysis/cron_account_analytic_account.py diff --git a/addons/account_analytic_analysis/__init__.py b/addons/account_analytic_analysis/__init__.py index a7bdb11ad67..429aa011f31 100644 --- a/addons/account_analytic_analysis/__init__.py +++ b/addons/account_analytic_analysis/__init__.py @@ -20,6 +20,7 @@ ############################################################################## import account_analytic_analysis +import cron_account_analytic_account # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account_analytic_analysis/__openerp__.py b/addons/account_analytic_analysis/__openerp__.py index 588055b3741..f5dea6e77e0 100644 --- a/addons/account_analytic_analysis/__openerp__.py +++ b/addons/account_analytic_analysis/__openerp__.py @@ -41,6 +41,7 @@ user-wise as well as month wise. "security/ir.model.access.csv", "account_analytic_analysis_view.xml", "account_analytic_analysis_menu.xml", + "account_analytic_analysis_cron.xml", ], 'demo_xml' : [], 'installable': True, diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py index 11f92614b73..03dea210396 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis.py +++ b/addons/account_analytic_analysis/account_analytic_analysis.py @@ -374,7 +374,25 @@ class account_analytic_account(osv.osv): res[id] = round(res.get(id, 0.0),2) return res - _columns ={ + def _is_overdue_quantity(self, cr, uid, ids, fieldnames, args, context=None): + result = dict.fromkeys(ids, 0) + + for record in self.browse(cr, uid, ids, context=context): + result[record.id] = int(record.quantity >= record.quantity_max) + + return result + + def _get_analytic_account(self, cr, uid, ids, context=None): + result = set() + for line in self.pool.get('account.analytic.line').browse(cr, uid, ids, context=context): + result.add(line.account_id.id) + return list(result) + + _columns = { + 'is_overdue_quantity' : fields.function(_is_overdue_quantity, method=True, type='boolean', string='Overdue Quantity', + store={ + 'account.analytic.line' : (_get_analytic_account, None, 20), + }), 'ca_invoiced': fields.function(_ca_invoiced_calc, method=True, type='float', string='Invoiced Amount', help="Total customer invoiced amount for this account.", digits_compute=dp.get_precision('Account')), diff --git a/addons/account_analytic_analysis/account_analytic_analysis_cron.xml b/addons/account_analytic_analysis/account_analytic_analysis_cron.xml new file mode 100644 index 00000000000..7134d59414b --- /dev/null +++ b/addons/account_analytic_analysis/account_analytic_analysis_cron.xml @@ -0,0 +1,15 @@ + + + + + Analytic Account Report for Sales + 1 + weeks + -1 + + + + + + + diff --git a/addons/account_analytic_analysis/account_analytic_analysis_menu.xml b/addons/account_analytic_analysis/account_analytic_analysis_menu.xml index badd7e1816a..a564db17dfc 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis_menu.xml +++ b/addons/account_analytic_analysis/account_analytic_analysis_menu.xml @@ -21,5 +21,63 @@ [('date','<=',time.strftime('%Y-%m-%d')),('state','=','open')] + + + account.analytic.account.search + account.analytic.account + search + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Overdue Accounts + account.analytic.account + form + tree,form,graph + {'search_default_has_partner' : 1, 'search_default_current' : 1} + + + + + diff --git a/addons/account_analytic_analysis/cron_account_analytic_account.py b/addons/account_analytic_analysis/cron_account_analytic_account.py new file mode 100644 index 00000000000..2bc8e0f98b1 --- /dev/null +++ b/addons/account_analytic_analysis/cron_account_analytic_account.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +from osv import osv +from mako.template import Template +import time +try: + import cStringIO as StringIO +except ImportError: + import StringIO + +import tools + +MAKO_TEMPLATE = u"""Hello ${user.name}, + +Here is a list of contracts that have to be renewed for two +possible reasons: + - the end of contract date is passed + - the customer consumed more support hours than expected + +Can you contact the customer in order to sell a new or renew its contract. +the contract has been set with a pending state, can you update the status +of the analytic account following this rule: + - Set Done: if the customer does not want to renew + - Set Open: if the customer purchased an extra contract + +Here is the list of contracts to renew: +% for partner, accounts in partners.iteritems(): + * ${partner.name} + % for account in accounts: + - Name: ${account.name} + % if account.quantity_max != 0.0: + - Quantity: ${account.quantity}/${account.quantity_max} hours + % endif + - Dates: ${account.date_start} to ${account.date and account.date or '???'} + - Contacts: + % for address in account.partner_id.address: + . ${address.name}, ${address.phone}, ${address.email} + % endfor + + % endfor +% endfor + +You can use the report in the menu: Sales > Invoicing > Overdue Accounts + +Regards, + +-- +OpenERP +""" + +class analytic_account(osv.osv): + _inherit = 'account.analytic.account' + + def cron_account_analytic_account(self, cr, uid, context=None): + domain = [ + ('name', 'not ilike', 'maintenance'), + ('partner_id', '!=', False), + ('user_id', '!=', False), + ('user_id.user_email', '!=', False), + ('state', 'in', ('draft', 'open')), + '|', ('date', '<', time.strftime('%Y-%m-%d')), ('date', '=', False), + ] + + account_ids = self.search(cr, uid, domain, context=context, order='name asc') + accounts = self.browse(cr, uid, account_ids, context=context) + + users = dict() + for account in accounts: + users.setdefault(account.user_id, dict()).setdefault(account.partner_id, []).append(account) + + account.write({'state' : 'pending'}, context=context) + + for user, data in users.iteritems(): + subject = '[OPENERP] Reporting: Analytic Accounts' + body = Template(MAKO_TEMPLATE).render_unicode(user=user, partners=data) + tools.email_send('noreply@openerp.com', [user.user_email, ], subject, body) + + return True + +analytic_account() From 8fee569a7b4ea90ee4946975fdb6f3ad3d130c8f Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 22 Apr 2011 13:37:06 +0200 Subject: [PATCH 02/11] [REF] Clean up bzr revid: stephane@openerp.com-20110422113706-kyeoyfb79uvc5899 --- addons/base_action_rule/base_action_rule.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 2a4e2c0e4e1..869c8ea1f03 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -325,7 +325,7 @@ the rule to mark CC(mail to any other person defined in actions)."), @param context: A standard dictionary for contextual values """ if context is None: context = {} - ok = True + ok = True if action.filter_id: if action.model_id.model == action.filter_id.model_id: context.update(eval(action.filter_id.context)) @@ -445,19 +445,13 @@ the rule to mark CC(mail to any other person defined in actions)."), context = {} context.update({'action': True}) - if not scrit: - scrit = [] for action in self.browse(cr, uid, ids, context=context): - model_obj = self.pool.get(action.model_id.model) for obj in objects: - ok = self.do_check(cr, uid, action, obj, context=context) - if not ok: - continue + if self.do_check(cr, uid, action, obj, context=context): + model_obj = self.pool.get(action.model_id.model) + self.do_action(cr, uid, action, model_obj, obj, context=context) - if ok: - self.do_action(cr, uid, action, model_obj, obj, context) - break context.update({'action': False}) return True @@ -480,7 +474,7 @@ the rule to mark CC(mail to any other person defined in actions)."), return True _constraints = [ - (_check_mail, 'Error: The mail is not well formated', ['act_mail_body']), + (_check_mail, 'Error: The mail is not well formatted', ['act_mail_body']), ] base_action_rule() From 63573d48d887a2906a54d9a08828984030acf8d0 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 22 Apr 2011 13:41:11 +0200 Subject: [PATCH 03/11] [FIX] Use the last description where the name is not a state of a CRM case bzr revid: stephane@openerp.com-20110422114111-pb1boou0lf3q9rlh --- addons/crm/crm.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index f25e4c1a0bf..227b19db73e 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -466,6 +466,11 @@ class crm_case(object): src = case_email dest = case.user_id body = case.description or "" + for message in case.message_ids: + if message.name not in AVAILABLE_STATES.values(): + body = message.description + break + if case.message_ids: body = case.message_ids[0].description or "" if not destination: @@ -486,7 +491,7 @@ class crm_case(object): attach_to_send = map(lambda x: (x['datas_fname'], base64.decodestring(x['datas'])), attach_to_send) # Send an email - subject = "Reminder: [%s] %s" % (str(case.id), case.name,) + subject = "[%s] %s" % (str(case.id), case.name,) tools.email_send( src, [dest], From 65f1b3bcfb68db284e5fb530ba6a8e32f23a4386 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 22 Apr 2011 13:41:43 +0200 Subject: [PATCH 04/11] [FIX] Apply the change and call the do_action callback bzr revid: stephane@openerp.com-20110422114143-vum7ebti5q3lutip --- addons/crm/crm_action_rule.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/crm/crm_action_rule.py b/addons/crm/crm_action_rule.py index 477f9132fab..62948a1d650 100644 --- a/addons/crm/crm_action_rule.py +++ b/addons/crm/crm_action_rule.py @@ -107,9 +107,7 @@ this if you want the rule to send an email to the partner."), @param cr: the current row, from the database cursor, @param uid: the current user’s ID for security checks, @param context: A standard dictionary for contextual values """ - res = super(base_action_rule, self).do_action(cr, uid, action, model_obj, obj, context=context) write = {} - if hasattr(action, 'act_section_id') and action.act_section_id: obj.section_id = action.act_section_id write['section_id'] = action.act_section_id.id @@ -127,6 +125,7 @@ this if you want the rule to send an email to the partner."), model_obj._history(cr, uid, [obj], _(action.act_state)) model_obj.write(cr, uid, [obj.id], write, context) + super(base_action_rule, self).do_action(cr, uid, action, model_obj, obj, context=context) emails = [] if hasattr(obj, 'email_from') and action.act_mail_to_partner: From 1704c9f8822f77066ca78136f485bec995484716 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 22 Apr 2011 13:43:07 +0200 Subject: [PATCH 05/11] [REF] Use the set_reply_to function if this one exists in the object bzr revid: stephane@openerp.com-20110422114307-dyymgrgz0j4e1ace --- addons/crm/wizard/crm_send_email.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/addons/crm/wizard/crm_send_email.py b/addons/crm/wizard/crm_send_email.py index 21902d9ece4..0282ddd346b 100644 --- a/addons/crm/wizard/crm_send_email.py +++ b/addons/crm/wizard/crm_send_email.py @@ -212,9 +212,13 @@ class crm_send_new_email(osv.osv_memory): res.update({'email_to': ''}) if 'email_from' in fields: res.update({'email_from': user_mail_from and tools.ustr(user_mail_from) or ''}) + if 'reply_to' in fields: - if hasattr(case, 'section_id'): - res.update({'reply_to': case.section_id and case.section_id.reply_to or False}) + if hasattr(case, 'set_reply_to'): + case.set_reply_to(res) + elif hasattr(case, 'section_id'): + res.update({'reply_to' : case.section_id and case.section_id.reply_to or False}) + if 'subject' in fields: res.update({'subject': tools.ustr(context.get('subject', case.name) or '')}) if context.get('mass_mail'): @@ -276,9 +280,10 @@ class crm_send_new_email(osv.osv_memory): if 'email_cc' in fields: email_cc = (case.email_cc and tools.ustr(case.email_cc) + ', ' or '') + (hist.email_cc or '') res.update({'email_cc': email_cc}) - if 'reply_to' in fields: - if hasattr(case, 'section_id'): - res.update({'reply_to': case.section_id.reply_to or ''}) + if hasattr(case, 'set_reply_to'): + case.set_reply_to(res) + elif hasattr(case, 'section_id'): + res.update({'reply_to': case.section_id.reply_to or ''}) if 'state' in fields: res['state'] = u'pending' return res From 6063f74810a9e2e510ec87f83709542c6a0c3f34 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 22 Apr 2011 13:43:35 +0200 Subject: [PATCH 06/11] [FIX] Catch the Exception and not the all bzr revid: stephane@openerp.com-20110422114335-jxm4jrbkw1hw24t3 --- .../mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py b/addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py index 204cb2d5d03..e30741c882e 100644 --- a/addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py +++ b/addons/mail_gateway/scripts/openerp_mailgate/openerp_mailgate.py @@ -86,7 +86,7 @@ if __name__ == '__main__': custom_values = {} try: custom_values = dict(eval(options.custom_values)) - except: + except Exception: pass parser.parse(msg_txt, custom_values) From dcf034bd52a861454a5dd7f10b0af5409468ba13 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 22 Apr 2011 13:44:23 +0200 Subject: [PATCH 07/11] [FIX] Use the Responsible (user_id) of the case as the default value in the sheet line. [FIX] Remove the on_change event on the analytic account in the worklogs bzr revid: stephane@openerp.com-20110422114423-fhqluv4tp1uho6x7 --- addons/project_issue_sheet/project_issue_sheet_view.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/project_issue_sheet/project_issue_sheet_view.xml b/addons/project_issue_sheet/project_issue_sheet_view.xml index 058fd67a946..4de1caeddd3 100644 --- a/addons/project_issue_sheet/project_issue_sheet_view.xml +++ b/addons/project_issue_sheet/project_issue_sheet_view.xml @@ -18,11 +18,11 @@ - - + + - + From c56df0436089193581ccdf961f51442b96b38152 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 22 Apr 2011 13:46:05 +0200 Subject: [PATCH 08/11] [FIX] Set the user_id (Responsible) field when the user clicks up on the "Open" button. [FIX] There was a bug in the computation function for the working_hours, delay_open, ... fields. [FIX] The user_id (Responsible) is replaced by a many2one instead of a related field. [IMP] Escalation: The user_id (Responsible) is the project manager of the project escalation, if there is no project manager, let's empty. bzr revid: stephane@openerp.com-20110422114605-nlle7h9mpqq9yj00 --- addons/project_issue/project_issue.py | 43 ++++++++++++------- addons/project_issue/project_issue_view.xml | 21 +++++---- .../report/project_issue_report_view.xml | 8 ++-- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py index a8a32847243..e6ebf64e453 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -56,7 +56,7 @@ class project_issue(crm.crm_case, osv.osv): """ res = super(project_issue, self).case_open(cr, uid, ids, *args) - self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S'), 'assigned_to' : uid}) + self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S'), 'user_id' : uid}) for (id, name) in self.name_get(cr, uid, ids): message = _("Issue '%s' has been opened.") % name self.log(cr, uid, id, message) @@ -90,32 +90,32 @@ class project_issue(crm.crm_case, osv.osv): res = {} for issue in self.browse(cr, uid, ids, context=context): + res[issue.id] = {} for field in fields: - res[issue.id] = {} duration = 0 ans = False hours = 0 + date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S") if field in ['working_hours_open','day_open']: if issue.date_open: - date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S") date_open = datetime.strptime(issue.date_open, "%Y-%m-%d %H:%M:%S") ans = date_open - date_create date_until = issue.date_open #Calculating no. of working hours to open the issue hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id, - datetime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'), - datetime.strptime(issue.date_open, '%Y-%m-%d %H:%M:%S')) + date_create, + date_open) elif field in ['working_hours_close','day_close']: if issue.date_closed: - date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S") date_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S") date_until = issue.date_closed ans = date_close - date_create #Calculating no. of working hours to close the issue hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id, - datetime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'), - datetime.strptime(issue.date_closed, '%Y-%m-%d %H:%M:%S')) + date_create, + date_close) + if ans: resource_id = False if issue.user_id: @@ -125,7 +125,11 @@ class project_issue(crm.crm_case, osv.osv): duration = float(ans.days) if issue.project_id and issue.project_id.resource_calendar_id: duration = float(ans.days) * 24 - new_dates = cal_obj.interval_min_get(cr, uid, issue.project_id.resource_calendar_id.id, datetime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'), duration, resource=resource_id) + + new_dates = cal_obj.interval_min_get(cr, uid, + issue.project_id.resource_calendar_id.id, + date_create, + duration, resource=resource_id) no_days = [] date_until = datetime.strptime(date_until, '%Y-%m-%d %H:%M:%S') for in_time, out_time in new_dates: @@ -134,10 +138,12 @@ class project_issue(crm.crm_case, osv.osv): if out_time > date_until: break duration = len(no_days) + if field in ['working_hours_open','working_hours_close']: res[issue.id][field] = hours else: res[issue.id][field] = abs(float(duration)) + return res def _get_issue_task(self, cr, uid, ids, context=None): @@ -175,7 +181,7 @@ class project_issue(crm.crm_case, osv.osv): 'section_id': fields.many2one('crm.case.section', 'Sales Team', \ select=True, help='Sales team to which Case belongs to.\ Define Responsible user and Email account for mail gateway.'), - 'user_id': fields.related('project_id', 'user_id', type='many2one', relation='res.users', store=True, select=1, string='Responsible'), + 'user_id' : fields.many2one('res.users', select=1, string='Responsible'), 'partner_id': fields.many2one('res.partner', 'Partner'), 'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \ domain="[('partner_id','=',partner_id)]"), @@ -282,7 +288,7 @@ class project_issue(crm.crm_case, osv.osv): 'date': bug.date, 'project_id': bug.project_id.id, 'priority': bug.priority, - 'user_id': bug.assigned_to.id, + 'user_id': bug.user_id.id, 'planned_hours': 0.0, }) @@ -362,13 +368,10 @@ class project_issue(crm.crm_case, osv.osv): """ cases = self.browse(cr, uid, ids) for case in cases: - data = {} + data = {'state' : 'draft'} if case.project_id.project_escalation_id: data['project_id'] = case.project_id.project_escalation_id.id - if case.project_id.project_escalation_id.user_id: - data['user_id'] = case.project_id.project_escalation_id.user_id.id - if case.task_id: - self.pool.get('project.task').write(cr, uid, [case.task_id.id], {'project_id': data['project_id'], 'user_id': False}) + data['user_id'] = case.project_id.project_escalation_id.user_id and case.project_id.project_escalation_id.user_id.id or False else: raise osv.except_osv(_('Warning !'), _('You cannot escalate this issue.\nThe relevant Project has not configured the Escalation Project!')) self.write(cr, uid, [case.id], data) @@ -487,6 +490,14 @@ class project_issue(crm.crm_case, osv.osv): return super(project_issue, self).copy(cr, uid, id, default=default, context=context) + #def set_reply_to(self, cr, uid, oid, values, context=None): + # default_value = False + + # if oid: + # this = self.browse(cr, uid, oid, context=context)[0] + # if this.section_id: + # values['reply_to'] = this.section_id.email or default_value + project_issue() class project(osv.osv): diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index 2224527e9d2..34d429b4352 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -55,8 +55,7 @@ - - + @@ -75,7 +74,7 @@ - +