[imp] merge + cleaning: contract managemeent, not perfect yet, but good enough -> to replace static email one day

bzr revid: fp@tinyerp.com-20111108225337-5muyehnvgqasv44i
This commit is contained in:
Fabien Pinckaers 2011-11-08 23:53:37 +01:00
commit 3d9b149dcc
13 changed files with 209 additions and 28 deletions

View File

@ -20,6 +20,7 @@
############################################################################## ##############################################################################
import account_analytic_analysis import account_analytic_analysis
import cron_account_analytic_account
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -42,6 +42,7 @@ user-wise as well as month wise.
"security/ir.model.access.csv", "security/ir.model.access.csv",
"account_analytic_analysis_view.xml", "account_analytic_analysis_view.xml",
"account_analytic_analysis_menu.xml", "account_analytic_analysis_menu.xml",
"account_analytic_analysis_cron.xml",
], ],
'demo_xml' : [], 'demo_xml' : [],
'installable': True, 'installable': True,

View File

@ -374,7 +374,28 @@ class account_analytic_account(osv.osv):
res[id] = round(res.get(id, 0.0),2) res[id] = round(res.get(id, 0.0),2)
return res 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):
if record.quantity == 0.0 and record.quantity_max == 0.0:
result[record.id] = 0
else:
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, type='float', string='Invoiced Amount', 'ca_invoiced': fields.function(_ca_invoiced_calc, type='float', string='Invoiced Amount',
help="Total customer invoiced amount for this account.", help="Total customer invoiced amount for this account.",
digits_compute=dp.get_precision('Account')), digits_compute=dp.get_precision('Account')),

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding='UTF-8'?>
<openerp>
<data>
<record model="ir.cron" id="account_analytic_cron">
<field name="name">Analytic Account Report for Sales</field>
<field name="interval_number">1</field>
<field name="interval_type">weeks</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall"/>
<field eval="'account.analytic.account'" name="model"/>
<field eval="'cron_account_analytic_account'" name="function"/>
<field eval="'()'" name="args"/>
</record>
</data>
</openerp>

View File

@ -20,6 +20,67 @@
<field name="view_mode">tree,form,graph</field> <field name="view_mode">tree,form,graph</field>
<field name="domain">[('date','&lt;=',time.strftime('%Y-%m-%d')),('state','=','open')]</field> <field name="domain">[('date','&lt;=',time.strftime('%Y-%m-%d')),('state','=','open')]</field>
</record> </record>
<menuitem action="action_account_analytic_managed_overpassed" id="menu_action_account_analytic_managed_overpassed" sequence="50" parent="menu_invoicing" groups="analytic.group_analytic_accounting"/> <menuitem action="action_account_analytic_managed_overpassed" id="menu_action_account_analytic_managed_overpassed" sequence="50" parent="menu_invoicing" groups="base.group_extended"/>
<record id="view_account_analytic_account_overdue_search" model="ir.ui.view">
<field name="name">account.analytic.account.search</field>
<field name="model">account.analytic.account</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Analytic Account">
<group col="8" colspan="4">
<filter icon="terp-check" name="draft" string="Draft" domain="[('state','=','draft')]" help="Contracts not signed yet"/>
<filter icon="terp-camera_test" name="open" string="Open" domain="[('state','=','open')]" help="Contracts in progress"/>
<filter icon="terp-gtk-media-pause" name="pending" string="Pending" domain="[('state','=','pending')]" help="Pending contracts to renew with your customer"/>
<separator orientation="vertical"/>
<filter icon="terp-go-today" string="To Renew" domain="['|', '&amp;', ('date', '!=', False), ('date', '&lt;', time.strftime('%%Y-%%m-%%d')), ('is_overdue_quantity', '=', True)]"
name="renew"
help="The contracts to be renewed because the deadline is passed or the working hours are higher than the allocated hours" />
<filter icon="terp-go-month"
string=" +1 Month"
domain="[('date','&lt;=', (datetime.date.today() + relativedelta(months=1)).strftime('%%Y-%%m-%%d')),('date','>=', time.strftime('%%Y-%%m-%%d'))]"
help="Analytic Accounts with a past deadline in one month." />
<separator orientation="vertical"/>
<field name="name" select="1"/>
<field name="code" select="1"/>
<field name="partner_id" select="1">
<filter string="Has Partner" name="has_partner" domain="[('partner_id', '!=', False)]" icon="terp-partner" />
<filter string="Has No Partner" name="has_no_partner" domain="[('partner_id', '=', False)]" icon="terp-partner" />
</field>
<field name="user_id">
<filter string="My Accounts" domain="[('user_id','=',uid)]" icon="terp-personal" name="my_accounts" />
<filter string="No Account Manager" domain="[('user_id', '=', False)]" icon="terp-personal-" />
</field>
</group>
<newline/>
<group expand="0" string="Group By...">
<filter string="Manager" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Associated Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/>
<separator orientation="vertical"/>
<filter string="Parent" icon="terp-folder-orange" domain="[]" context="{'group_by':'parent_id'}"/>
<!--
<filter string="Start Date" icon="terp-go-month" domain="[]" context="{'group_by' : 'date_start'}" />
-->
<filter string="End Date" icon="terp-go-month" domain="[]" context="{'group_by' : 'date'}" />
</group>
</search>
</field>
</record>
<record id="action_account_analytic_overdue" model="ir.actions.act_window">
<field name="name">Overdue Accounts</field>
<field name="res_model">account.analytic.account</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,graph</field>
<field name="context">{'search_default_has_partner':1, 'search_default_my_accounts':1, 'search_default_draft':1, 'search_default_pending':1, 'search_default_open':1, 'search_default_renew':1}</field>
<field name="domain">[('type','=','normal')]</field>
<field name="search_view_id" ref="view_account_analytic_account_overdue_search"/>
<field name="help">You will find here the contracts to be renewed because the deadline is passed or the working hours are higher than the allocated hours. OpenERP automatically sets these analytic accounts to the pending state, in order to raise a warning during the timesheets recording. Salesmen should review all pending accounts and reopen or close the according to the negotiation with the customer.</field>
</record>
<menuitem action="action_account_analytic_overdue" id="menu_action_account_analytic_overdue" sequence="50" parent="sale.menu_invoiced" groups="base.group_extended"/>
</data> </data>
</openerp> </openerp>

View File

@ -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 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()

View File

@ -446,15 +446,11 @@ the rule to mark CC(mail to any other person defined in actions)."),
scrit = [] scrit = []
for action in self.browse(cr, uid, ids, context=context): for action in self.browse(cr, uid, ids, context=context):
model_obj = self.pool.get(action.model_id.model)
for obj in objects: for obj in objects:
ok = self.do_check(cr, uid, action, obj, context=context) if self.do_check(cr, uid, action, obj, context=context):
if not ok: model_obj = self.pool.get(action.model_id.model)
continue 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}) context.update({'action': False})
return True return True

View File

@ -486,8 +486,11 @@ class crm_case(crm_base):
src = case_email src = case_email
dest = case.user_id.user_email or "" dest = case.user_id.user_email or ""
body = case.description or "" body = case.description or ""
if case.message_ids: for message in case.message_ids:
body = case.message_ids[0].body_text or "" if message.email_from:
body = message.description
break
if not destination: if not destination:
src, dest = dest, case.email_from src, dest = dest, case.email_from
if body and case.user_id.signature: if body and case.user_id.signature:

View File

@ -92,7 +92,6 @@ class base_action_rule(osv.osv):
def do_action(self, cr, uid, action, model_obj, obj, context=None): def do_action(self, cr, uid, action, model_obj, obj, context=None):
res = super(base_action_rule, self).do_action(cr, uid, action, model_obj, obj, context=context) res = super(base_action_rule, self).do_action(cr, uid, action, model_obj, obj, context=context)
write = {} write = {}
if hasattr(action, 'act_section_id') and action.act_section_id: if hasattr(action, 'act_section_id') and action.act_section_id:
obj.section_id = action.act_section_id obj.section_id = action.act_section_id
write['section_id'] = action.act_section_id.id write['section_id'] = action.act_section_id.id
@ -110,6 +109,7 @@ class base_action_rule(osv.osv):
model_obj.message_append(cr, uid, [obj], _(action.act_state)) model_obj.message_append(cr, uid, [obj], _(action.act_state))
model_obj.write(cr, uid, [obj.id], write, context) 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 = [] emails = []
if hasattr(obj, 'email_from') and action.act_mail_to_partner: if hasattr(obj, 'email_from') and action.act_mail_to_partner:

View File

@ -74,7 +74,7 @@ class mail_message_common(osv.osv_memory):
'model': fields.char('Related Document model', size=128, select=1, readonly=1), 'model': fields.char('Related Document model', size=128, select=1, readonly=1),
'res_id': fields.integer('Related Document ID', select=1, readonly=1), 'res_id': fields.integer('Related Document ID', select=1, readonly=1),
'date': fields.datetime('Date'), 'date': fields.datetime('Date'),
'email_from': fields.char('From', size=128, help='Message sender, taken from user preferences'), 'email_from': fields.char('From', size=128, help='Message sender, taken from user preferences. If empty, this is not a mail but a message.'),
'email_to': fields.char('To', size=256, help='Message recipients'), 'email_to': fields.char('To', size=256, help='Message recipients'),
'email_cc': fields.char('Cc', size=256, help='Carbon copy message recipients'), 'email_cc': fields.char('Cc', size=256, help='Carbon copy message recipients'),
'email_bcc': fields.char('Bcc', size=256, help='Blind carbon copy message recipients'), 'email_bcc': fields.char('Bcc', size=256, help='Blind carbon copy message recipients'),

View File

@ -98,26 +98,25 @@ class project_issue(crm.crm_case, osv.osv):
ans = False ans = False
hours = 0 hours = 0
date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
if field in ['working_hours_open','day_open']: if field in ['working_hours_open','day_open']:
if issue.date_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") date_open = datetime.strptime(issue.date_open, "%Y-%m-%d %H:%M:%S")
ans = date_open - date_create ans = date_open - date_create
date_until = issue.date_open date_until = issue.date_open
#Calculating no. of working hours to open the issue #Calculating no. of working hours to open the issue
hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id, 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'), date_create,
datetime.strptime(issue.date_open, '%Y-%m-%d %H:%M:%S')) date_open)
elif field in ['working_hours_close','day_close']: elif field in ['working_hours_close','day_close']:
if issue.date_closed: 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_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S")
date_until = issue.date_closed date_until = issue.date_closed
ans = date_close - date_create ans = date_close - date_create
#Calculating no. of working hours to close the issue #Calculating no. of working hours to close the issue
hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id, 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'), date_create,
datetime.strptime(issue.date_closed, '%Y-%m-%d %H:%M:%S')) date_close)
elif field in ['days_since_creation']: elif field in ['days_since_creation']:
if issue.create_date: if issue.create_date:
days_since_creation = datetime.today() - datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S") days_since_creation = datetime.today() - datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
@ -139,7 +138,11 @@ class project_issue(crm.crm_case, osv.osv):
duration = float(ans.days) duration = float(ans.days)
if issue.project_id and issue.project_id.resource_calendar_id: if issue.project_id and issue.project_id.resource_calendar_id:
duration = float(ans.days) * 24 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 = [] no_days = []
date_until = datetime.strptime(date_until, '%Y-%m-%d %H:%M:%S') date_until = datetime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
for in_time, out_time in new_dates: for in_time, out_time in new_dates:
@ -148,10 +151,12 @@ class project_issue(crm.crm_case, osv.osv):
if out_time > date_until: if out_time > date_until:
break break
duration = len(no_days) duration = len(no_days)
if field in ['working_hours_open','working_hours_close']: if field in ['working_hours_open','working_hours_close']:
res[issue.id][field] = hours res[issue.id][field] = hours
else: else:
res[issue.id][field] = abs(float(duration)) res[issue.id][field] = abs(float(duration))
return res return res
def _get_issue_task(self, cr, uid, ids, context=None): def _get_issue_task(self, cr, uid, ids, context=None):
@ -365,7 +370,6 @@ class project_issue(crm.crm_case, osv.osv):
self.write(cr, uid, task.id, {'type_id': index and types[index-1] or False}) self.write(cr, uid, task.id, {'type_id': index and types[index-1] or False})
return True return True
def onchange_task_id(self, cr, uid, ids, task_id, context=None): def onchange_task_id(self, cr, uid, ids, task_id, context=None):
result = {} result = {}
if not task_id: if not task_id:
@ -383,7 +387,7 @@ class project_issue(crm.crm_case, osv.osv):
""" """
cases = self.browse(cr, uid, ids) cases = self.browse(cr, uid, ids)
for case in cases: for case in cases:
data = {} data = {'state' : 'draft'}
if case.project_id.project_escalation_id: if case.project_id.project_escalation_id:
data['project_id'] = case.project_id.project_escalation_id.id data['project_id'] = case.project_id.project_escalation_id.id
if case.project_id.project_escalation_id.user_id: if case.project_id.project_escalation_id.user_id:

View File

@ -55,7 +55,7 @@
<field name="name"/> <field name="name"/>
<field name="project_id" required="True" on_change="on_change_project(project_id)"/> <field name="project_id" required="True" on_change="on_change_project(project_id)"/>
<field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'project.issue')]"/> <field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'project.issue')]"/>
<field name="user_id"/> <field name="user_id"/>
<field name="version_id" colspan="2" widget="selection"/> <field name="version_id" colspan="2" widget="selection"/>
<group colspan="2" col="4"> <group colspan="2" col="4">
<field name="type_id" string="Resolution" /> <field name="type_id" string="Resolution" />
@ -188,7 +188,7 @@
<button name="next_type" string="Next" type="object" icon="gtk-go-forward" help="Change to Next Stage"/> <button name="next_type" string="Next" type="object" icon="gtk-go-forward" help="Change to Next Stage"/>
<field name="version_id" widget="selection"/> <field name="version_id" widget="selection"/>
<field name="user_id"/> <field name="user_id"/>
<field name="progress" widget="progressbar"/> <field name="progress" widget="progressbar" attrs="{'invisible':[('task_id','=',False)]}"/>
<field name="state"/> <field name="state"/>
<button name="case_cancel" string="Cancel" states="draft,open,pending" type="object" icon="gtk-cancel"/> <button name="case_cancel" string="Cancel" states="draft,open,pending" type="object" icon="gtk-cancel"/>
<button name="case_close" string="Done" states="open,draft,pending" type="object" icon="gtk-jump-to"/> <button name="case_close" string="Done" states="open,draft,pending" type="object" icon="gtk-jump-to"/>
@ -372,7 +372,7 @@
</record> </record>
<record id="view_project_feature_filter" model="ir.ui.view"> <record id="view_project_feature_filter" model="ir.ui.view">
<field name="name">Project Issue- Feature Tracker Search</field> <field name="name">Project Issue - Feature Tracker Search</field>
<field name="model">project.issue</field> <field name="model">project.issue</field>
<field name="type">search</field> <field name="type">search</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
@ -384,8 +384,8 @@
<field name="name" string="Feature description"/> <field name="name" string="Feature description"/>
<field name="user_id"/> <field name="user_id"/>
<field name="state"> <field name="state">
<filter icon="terp-check" domain="[('state','in',('open','draft'))]" help="Current Features" name="current_feature"/> <filter icon="terp-check" domain="[('state','in',('open','draft'))]" help="Current Features" name="current_feature"/>
<filter icon="terp-camera_test" domain="[('state','=','open')]" help="Open Features"/> <filter icon="terp-camera_test" domain="[('state','=','open')]" help="Open Features"/>
</field> </field>
<field name="project_id" string="Project"/> <field name="project_id" string="Project"/>
</group> </group>

View File

@ -19,7 +19,7 @@
<notebook colspan="4"> <notebook colspan="4">
<page string="Worklogs"> <page string="Worklogs">
<field name="timesheet_ids" colspan="4" nolabel="1" context="{'default_user_id' : user_id, 'default_account_id' : analytic_account_id}"> <field name="timesheet_ids" colspan="4" nolabel="1" context="{'default_user_id' : user_id, 'default_account_id' : analytic_account_id}">
<tree editable="top" string="Timesheet"> <tree editable="top" string="Timesheets">
<field name="name"/> <field name="name"/>
<field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time"/> <field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time"/>
<field name="account_id" invisible="0" domain="[('partner_id', '=', parent.partner_id)]" on_change="on_change_account_id(account_id)"/> <field name="account_id" invisible="0" domain="[('partner_id', '=', parent.partner_id)]" on_change="on_change_account_id(account_id)"/>