bzr revid: mra@tinyerp.com-20100514092924-olagh9in1ta0opiy
This commit is contained in:
mra (Open ERP) 2010-05-14 14:59:24 +05:30
commit 9adbbcab87
231 changed files with 12701 additions and 10133 deletions

View File

@ -160,5 +160,17 @@ class timesheet_invoice(osv.osv):
)""")
timesheet_invoice()
class res_partner(osv.osv):
""" Inherits partner and adds contract information in the partner form """
_inherit = 'res.partner'
_columns = {
'contract_ids': fields.one2many('account.analytic.account', \
'partner_id', 'Contracts'),
}
res_partner()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -29,30 +29,30 @@
<group colspan="8">
<group colspan="4" height="450" width="600">
<field name="charts"/>
<group colspan="4" attrs="{'invisible':[('charts','!=','configurable')]}">
<field name="sale_tax" colspan="2"/>
<field name="purchase_tax" colspan="2"/>
<group colspan="4">
<separator col="4" colspan="4" string="Configure Fiscal Year"/>
<field name="date_start" on_change="on_change_start_date(date_start)"/>
<field name="date_stop"/>
<field name="period" colspan="4"/>
</group>
<group colspan="4" attrs="{'invisible':[('charts','!=','configurable')]}">
<separator col="4" colspan="4" string="Bank and Cost Account"/>
<field colspan="4" mode="tree" name="bank_accounts_id" nolabel="1" widget="one2many_list">
<form string="Bank Information">
<form string="">
<field name="acc_name"/>
<field name="account_type"/>
<field name="currency_id" widget="selection"/>
</form>
<tree editable="bottom" string="Bank Information">
<tree editable="bottom" string="">
<field name="acc_name"/>
<field name="account_type"/>
<field name="currency_id" widget="selection"/>
</tree>
</field>
</group>
<group colspan="4">
<separator col="4" colspan="4" string="Configure Fiscal Year"/>
<field name="date_start" on_change="on_change_start_date(date_start)"/>
<field name="date_stop"/>
<field name="period" colspan="4"/>
<group colspan="4" attrs="{'invisible':[('charts','!=','configurable')]}">
<field name="sale_tax" colspan="2" on_change="on_change_tax(sale_tax)"/>
<field name="purchase_tax" colspan="2" />
</group>
<group colspan="4" groups="base.group_extended">
<separator col="4" colspan="4" string="Install Extra Account Module"/>

View File

@ -478,6 +478,23 @@
<act_window domain="[('journal_id','=',active_id),('state','!=','draft'),('reconciled','=',False)]" id="act_account_journal_2_account_invoice_opened" name="Unpaid invoices" res_model="account.invoice" src_model="account.journal"/>
<act_window domain="[('account_analytic_id', '=', active_id)]" id="act_account_analytic_account_2_account_invoice_line" name="Invoice lines" res_model="account.invoice.line" src_model="account.analytic.account"/>
<!-- Partners inherited form -->
<record id="view_invoice_partner_info_form" model="ir.ui.view">
<field name="name">res.partner.invoice.info.inherit</field>
<field name="model">res.partner</field>
<field name="type">form</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<notebook position="inside">
<page string="Account Info">
<field name="invoice_ids" colspan="4" nolabel="1" context="{'group_by':'product_id'}"/>
</page>
</notebook>
</field>
</record>
</data>
</openerp>

View File

@ -7,13 +7,13 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.0\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2009-08-28 16:01+0000\n"
"PO-Revision-Date: 2010-05-10 08:37+0000\n"
"Last-Translator: eLBati - albatos.com <lorenzo.battistini@albatos.com>\n"
"PO-Revision-Date: 2010-05-13 14:03+0000\n"
"Last-Translator: Carlo - didotech.com <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2010-05-11 04:18+0000\n"
"X-Launchpad-Export-Date: 2010-05-14 04:55+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#. module: account
@ -2158,7 +2158,8 @@ msgid "Analytic Entry"
msgstr "Registrazione analitica"
#. module: account
#: view:res.company:0 field:res.company,overdue_msg:0
#: view:res.company:0
#: field:res.company,overdue_msg:0
msgid "Overdue Payments Message"
msgstr ""
@ -2231,7 +2232,7 @@ msgstr ""
#. module: account
#: rml:account.invoice:0
msgid "VAT :"
msgstr ""
msgstr "IVA"
#. module: account
#: wizard_field:account.general.ledger.report,account_selection,Account_list:0
@ -4475,7 +4476,7 @@ msgstr "Registrazioni contabili"
#. module: account
#: model:ir.actions.act_window,name:account.act_account_partner_account_move_unreconciled
msgid "Receivables & Payables"
msgstr "Invassi & Pagamenti"
msgstr "Incassi & Pagamenti"
#. module: account
#: rml:account.general.ledger:0

View File

@ -44,7 +44,7 @@ class account_installer(osv.osv_memory):
sorted(((m.name, m.shortdesc)
for m in modules.browse(cr, uid, ids)),
key=itemgetter(1)))
charts.insert(0,('configurable','Configurable Chart of Account'))
charts.insert(0,('configurable','Generic Chart Of Account'))
return charts
_columns = {
@ -81,10 +81,13 @@ class account_installer(osv.osv_memory):
'period':lambda *a:'month',
'sale_tax':lambda *a:0.0,
'purchase_tax':lambda *a:0.0,
'charts':'configurable',
#'charts':'configurable',
'bank_accounts_id':_get_default_accounts
}
def on_change_tax(self, cr, uid, id, tax):
return{'value':{'purchase_tax':tax}}
def on_change_start_date(self, cr, uid, id, start_date):
if start_date:
start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")

View File

@ -1496,4 +1496,15 @@ class account_invoice_tax(osv.osv):
return res
account_invoice_tax()
class res_partner(osv.osv):
""" Inherits partner and adds invoice information in the partner form """
_inherit = 'res.partner'
_columns = {
'invoice_ids': fields.one2many('account.invoice.line', 'partner_id', 'Invoices'),
}
res_partner()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -441,6 +441,21 @@
</graph>
</field>
</record>
<!-- Partners inherited form -->
<record id="view_contract_partner_info_form" model="ir.ui.view">
<field name="name">res.partner.contract.info.inherit</field>
<field name="model">res.partner</field>
<field name="type">form</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<field name="invoice_ids" position="after">
<field name="contract_ids" colspan="4" nolabel="1" />
</field>
</field>
</record>
</data>
</openerp>

View File

@ -33,20 +33,105 @@ class base_action_rule(osv.osv):
_name = 'base.action.rule'
_description = 'Action Rules'
def _state_get(self, cr, uid, context={}):
""" Get State
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values """
return self.state_get(cr, uid, context=context)
def state_get(self, cr, uid, context={}):
""" Get State
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values """
return [('', '')]
def priority_get(self, cr, uid, context={}):
""" Get Priority
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values """
return [('', '')]
_columns = {
'name': fields.many2one('ir.model', 'Model', required=True),
'max_level': fields.integer('Max Level', help='Specifies maximum level.'),
'rule_lines': fields.one2many('base.action.rule.line','rule_id','Rule Lines'),
'create_date': fields.datetime('Create Date', readonly=1),
'active': fields.boolean('Active')
'name': fields.many2one('ir.model', 'Object', required=True),
'max_level': fields.integer('Max Level', help='Specifies maximum level.'),
'create_date': fields.datetime('Create Date', readonly=1),
'active': fields.boolean('Active', help="If the active field is set to true,\
it will allow you to hide the rule without removing it."),
'sequence': fields.integer('Sequence', help="Gives the sequence order \
when displaying a list of rules."),
'trg_date_type': fields.selection([
('none', 'None'),
('create', 'Creation Date'),
('action_last', 'Last Action Date'),
('date', 'Date'),
('deadline', 'Deadline'),
], 'Trigger Date', size=16),
'trg_date_range': fields.integer('Delay after trigger date', \
help="Delay After Trigger Date,\
specifies you can put a negative number. If you need a delay before the \
trigger date, like sending a reminder 15 minutes before a meeting."),
'trg_date_range_type': fields.selection([('minutes', 'Minutes'), ('hour', 'Hours'), \
('day', 'Days'), ('month', 'Months')], 'Delay type'),
'trg_user_id': fields.many2one('res.users', 'Responsible'),
'trg_partner_id': fields.many2one('res.partner', 'Partner'),
'trg_partner_categ_id': fields.many2one('res.partner.category', 'Partner Category'),
'trg_state_from': fields.selection(_state_get, 'State', size=16),
'trg_state_to': fields.selection(_state_get, 'Button Pressed', size=16),
'act_method': fields.char('Call Object Method', size=64),
'act_user_id': fields.many2one('res.users', 'Set responsible to'),
'act_state': fields.selection(_state_get, 'Set state to', size=16),
'act_email_cc': fields.char('Add watchers (Cc)', size=250, help="\
These people will receive a copy of the future communication between partner \
and users by email"),
'act_remind_partner': fields.boolean('Remind Partner', help="Check \
this if you want the rule to send a reminder by email to the partner."),
'act_remind_user': fields.boolean('Remind responsible', help="Check \
this if you want the rule to send a reminder by email to the user."),
'act_reply_to': fields.char('Reply-To', size=64),
'act_remind_attach': fields.boolean('Remind with attachment', help="Check this if you want that all documents attached to the object be attached to the reminder email sent."),
'act_mail_to_user': fields.boolean('Mail to responsible', help="Check\
this if you want the rule to send an email to the responsible person."),
'act_mail_to_watchers': fields.boolean('Mail to watchers (CC)',
help="Check this if you want \
the rule to mark CC(mail to any other person defined in actions)."),
'act_mail_to_email': fields.char('Mail to these emails', size=128, \
help="Email-id of the persons whom mail is to be sent"),
'act_mail_body': fields.text('Mail body', help="Content of mail"),
'regex_name': fields.char('Regular Expression on Model Name', size=128),
'server_action_id': fields.many2one('ir.actions.server', 'Server Action', help="Describes the action name.\neg:on which object which action to be taken on basis of which condition"),
'filter_id':fields.many2one('ir.filters', 'Filter', required=False),
'domain':fields.char('Domain', size=124, required=False, readonly=False),
}
_defaults = {
'active': lambda *a: True,
'max_level': lambda *a: 15,
'active': lambda *a: True,
'max_level': lambda *a: 15,
'trg_date_type': lambda *a: 'none',
'trg_date_range_type': lambda *a: 'day',
'act_mail_to_user': lambda *a: 0,
'act_remind_partner': lambda *a: 0,
'act_remind_user': lambda *a: 0,
'act_mail_to_watchers': lambda *a: 0,
'domain': lambda *a: '[]'
}
_order = 'sequence'
def format_body(self, body):
""" Foramat Action rule's body
@param self: The object pointer """
@ -58,22 +143,22 @@ class base_action_rule(osv.osv):
@param self: The object pointer """
data = {
'object_id': obj.id,
'object_subject': hasattr(obj, 'name') and obj.name or False,
'object_date': hasattr(obj, 'date') and obj.date or False,
'object_description': hasattr(obj, 'description') and obj.description or False,
'object_user': hasattr(obj, 'user_id') and (obj.user_id and obj.user_id.name) or '/',
'object_id': obj.id,
'object_subject': hasattr(obj, 'name') and obj.name or False,
'object_date': hasattr(obj, 'date') and obj.date or False,
'object_description': hasattr(obj, 'description') and obj.description or False,
'object_user': hasattr(obj, 'user_id') and (obj.user_id and obj.user_id.name) or '/',
'object_user_email': hasattr(obj, 'user_id') and (obj.user_id and \
obj.user_id.address_id and obj.user_id.address_id.email) or '/',
obj.user_id.address_id and obj.user_id.address_id.email) or '/',
'object_user_phone': hasattr(obj, 'user_id') and (obj.user_id and\
obj.user_id.address_id and obj.user_id.address_id.phone) or '/',
'partner': hasattr(obj, 'partner_id') and (obj.partner_id and obj.partner_id.name) or '/',
obj.user_id.address_id and obj.user_id.address_id.phone) or '/',
'partner': hasattr(obj, 'partner_id') and (obj.partner_id and obj.partner_id.name) or '/',
'partner_email': hasattr(obj, 'partner_address_id') and (obj.partner_address_id and\
obj.partner_address_id.email) or '/',
obj.partner_address_id.email) or '/',
}
return self.format_body(body % data)
def email_send(self, cr, uid, obj, emails, body, emailfrom=tools.config.get('email_from',False), context={}):
def email_send(self, cr, uid, obj, emails, body, emailfrom=tools.config.get('email_from', False), context={}):
""" send email
@param self: The object pointer
@param cr: the current row, from the database cursor,
@ -92,7 +177,7 @@ class base_action_rule(osv.osv):
emailfrom = tools.ustr(emailfrom)
reply_to = emailfrom
if not emailfrom:
raise osv.except_osv(_('Error!'),
raise osv.except_osv(_('Error!'),
_("No E-Mail ID Found for your Company address!"))
return tools.email_send(emailfrom, emails, name, body, reply_to=reply_to, openobject_id=str(obj.id))
@ -105,6 +190,10 @@ class base_action_rule(osv.osv):
@param context: A standard dictionary for contextual values """
ok = True
if eval(action.domain):
obj_ids = obj._table.search(cr, uid, eval(action.domain), context=context)
if not obj.id in obj_ids:
ok = False
if hasattr(obj, 'user_id'):
ok = ok and (not action.trg_user_id.id or action.trg_user_id.id==obj.user_id.id)
if hasattr(obj, 'partner_id'):
@ -122,10 +211,6 @@ class base_action_rule(osv.osv):
if state_to:
ok = ok and (not action.trg_state_to or action.trg_state_to==state_to)
if hasattr(obj, 'priority'):
ok = ok and (not action.trg_priority_from or action.trg_priority_from>=obj.priority)
ok = ok and (not action.trg_priority_to or action.trg_priority_to<=obj.priority)
reg_name = action.regex_name
result_name = True
if reg_name:
@ -147,7 +232,7 @@ class base_action_rule(osv.osv):
@param context: A standard dictionary for contextual values """
if action.server_action_id:
context.update({'active_id':obj.id,'active_ids':[obj.id]})
context.update({'active_id':obj.id, 'active_ids':[obj.id]})
self.pool.get('ir.actions.server').run(cr, uid, [action.server_action_id.id], context)
write = {}
if hasattr(obj, 'user_id') and action.act_user_id:
@ -163,10 +248,6 @@ class base_action_rule(osv.osv):
obj.categ_id = action.act_categ_id
write['categ_id'] = action.act_categ_id.id
if hasattr(obj, 'priority') and action.act_priority:
obj.priority = action.act_priority
write['priority'] = action.act_priority
model_obj.write(cr, uid, [obj.id], write, context)
if hasattr(model_obj, 'remind_user') and action.act_remind_user:
@ -201,175 +282,58 @@ class base_action_rule(osv.osv):
if not scrit:
scrit = []
rule_line_obj = self.pool.get('base.action.rule.line')
for rule in self.browse(cr, uid, ids):
level = rule.max_level
cr.execute("select id from base_action_rule order by sequence")
rule_ids = map(lambda x: x[0], cr.fetchall())
for action in self.browse(cr, uid, rule_ids):
level = action.max_level
if not level:
break
newactions = []
scrit += [('rule_id','=',rule.id)]
line_ids = rule_line_obj.search(cr, uid, scrit)
actions = rule_line_obj.browse(cr, uid, line_ids, context=context)
model_obj = self.pool.get(rule.name.model)
model_obj = self.pool.get(action.name.model)
for obj in objects:
for action in actions:
ok = self.do_check(cr, uid, action, obj, context=context)
if not ok:
continue
ok = self.do_check(cr, uid, action, obj, context=context)
if not ok:
continue
base = False
if hasattr(obj, 'create_date') and action.trg_date_type=='create':
base = mx.DateTime.strptime(obj.create_date[:19], '%Y-%m-%d %H:%M:%S')
elif hasattr(obj, 'create_date') and action.trg_date_type=='action_last':
if hasattr(obj, 'date_action_last') and obj.date_action_last:
base = mx.DateTime.strptime(obj.date_action_last, '%Y-%m-%d %H:%M:%S')
else:
base = mx.DateTime.strptime(obj.create_date[:19], '%Y-%m-%d %H:%M:%S')
elif hasattr(obj, 'date_deadline') and action.trg_date_type=='deadline' \
and obj.date_deadline:
base = mx.DateTime.strptime(obj.date_deadline, '%Y-%m-%d %H:%M:%S')
elif hasattr(obj, 'date') and action.trg_date_type=='date' and obj.date:
base = mx.DateTime.strptime(obj.date, '%Y-%m-%d %H:%M:%S')
if base:
fnct = {
'minutes': lambda interval: mx.DateTime.RelativeDateTime(minutes=interval),
'day': lambda interval: mx.DateTime.RelativeDateTime(days=interval),
'hour': lambda interval: mx.DateTime.RelativeDateTime(hours=interval),
'month': lambda interval: mx.DateTime.RelativeDateTime(months=interval),
}
d = base + fnct[action.trg_date_range_type](action.trg_date_range)
dt = d.strftime('%Y-%m-%d %H:%M:%S')
ok = False
if hasattr(obj, 'date_action_last') and hasattr(obj, 'date_action_next'):
ok = (dt <= time.strftime('%Y-%m-%d %H:%M:%S')) and \
((not obj.date_action_next) or \
(dt >= obj.date_action_next and \
obj.date_action_last < obj.date_action_next))
if not ok:
if not obj.date_action_next or dt < obj.date_action_next:
obj.date_action_next = dt
model_obj.write(cr, uid, [obj.id], {'date_action_next': dt}, context)
base = False
if hasattr(obj, 'create_date') and action.trg_date_type=='create':
base = mx.DateTime.strptime(obj.create_date[:19], '%Y-%m-%d %H:%M:%S')
elif hasattr(obj, 'create_date') and action.trg_date_type=='action_last':
if hasattr(obj, 'date_action_last') and obj.date_action_last:
base = mx.DateTime.strptime(obj.date_action_last, '%Y-%m-%d %H:%M:%S')
else:
ok = action.trg_date_type=='none'
base = mx.DateTime.strptime(obj.create_date[:19], '%Y-%m-%d %H:%M:%S')
elif hasattr(obj, 'date_deadline') and action.trg_date_type=='deadline' \
and obj.date_deadline:
base = mx.DateTime.strptime(obj.date_deadline, '%Y-%m-%d %H:%M:%S')
elif hasattr(obj, 'date') and action.trg_date_type=='date' and obj.date:
base = mx.DateTime.strptime(obj.date, '%Y-%m-%d %H:%M:%S')
if base:
fnct = {
'minutes': lambda interval: mx.DateTime.RelativeDateTime(minutes=interval),
'day': lambda interval: mx.DateTime.RelativeDateTime(days=interval),
'hour': lambda interval: mx.DateTime.RelativeDateTime(hours=interval),
'month': lambda interval: mx.DateTime.RelativeDateTime(months=interval),
}
d = base + fnct[action.trg_date_range_type](action.trg_date_range)
dt = d.strftime('%Y-%m-%d %H:%M:%S')
ok = False
if hasattr(obj, 'date_action_last') and hasattr(obj, 'date_action_next'):
ok = (dt <= time.strftime('%Y-%m-%d %H:%M:%S')) and \
((not obj.date_action_next) or \
(dt >= obj.date_action_next and \
obj.date_action_last < obj.date_action_next))
if not ok:
if not obj.date_action_next or dt < obj.date_action_next:
obj.date_action_next = dt
model_obj.write(cr, uid, [obj.id], {'date_action_next': dt}, context)
else:
ok = action.trg_date_type=='none'
if ok:
self.do_action(cr, uid, action, model_obj, obj, context)
break
if ok:
self.do_action(cr, uid, action, model_obj, obj, context)
break
level -= 1
return True
base_action_rule()
class base_action_rule_line(osv.osv):
_name = 'base.action.rule.line'
_description = 'Action Rule Lines'
def _state_get(self, cr, uid, context={}):
""" Get State
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values """
return self.state_get(cr, uid, context=context)
def _priority_get(self, cr, uid, context={}):
""" Get Priority
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values """
return self.priority_get(cr, uid, context=context)
def state_get(self, cr, uid, context={}):
""" Get State
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values """
return [('','')]
def priority_get(self, cr, uid, context={}):
""" Get Priority
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values """
return [('','')]
_columns = {
'name': fields.char('Rule Name',size=64, required=True),
'rule_id': fields.many2one('base.action.rule','Rule'),
'active': fields.boolean('Active', help="If the active field is set to true,\
it will allow you to hide the rule without removing it."),
'sequence': fields.integer('Sequence', help="Gives the sequence order when\
displaying a list of rules."),
'trg_date_type': fields.selection([
('none','None'),
('create','Creation Date'),
('action_last','Last Action Date'),
('date','Date'),
('deadline', 'Deadline'),
], 'Trigger Date', size=16),
'trg_date_range': fields.integer('Delay after trigger date',help="Delay After Trigger Date,\
specifies you can put a negative number " \
"if you need a delay before the trigger date, like sending a reminder 15 minutes before a meeting."),
'trg_date_range_type': fields.selection([('minutes', 'Minutes'),('hour','Hours'),\
('day','Days'),('month','Months')], 'Delay type'),
'trg_user_id': fields.many2one('res.users', 'Responsible'),
'trg_partner_id': fields.many2one('res.partner', 'Partner'),
'trg_partner_categ_id': fields.many2one('res.partner.category', 'Partner Category'),
'trg_state_from': fields.selection(_state_get, 'State', size=16),
'trg_state_to': fields.selection(_state_get, 'Button Pressed', size=16),
'trg_priority_from': fields.selection(_priority_get, 'Minimum Priority'),
'trg_priority_to': fields.selection(_priority_get, 'Maximum Priority'),
'act_method': fields.char('Call Object Method', size=64),
'act_user_id': fields.many2one('res.users', 'Set responsible to'),
'act_state': fields.selection(_state_get, 'Set state to', size=16),
'act_priority': fields.selection(_priority_get, 'Set priority to'),
'act_email_cc': fields.char('Add watchers (Cc)', size=250, help="These people\
will receive a copy of the future communication between partner and users by email"),
'act_remind_partner': fields.boolean('Remind Partner', help="Check this if\
you want the rule to send a reminder by email to the partner."),
'act_remind_user': fields.boolean('Remind responsible', help="Check this if \
you want the rule to send a reminder by email to the user."),
'act_reply_to': fields.char('Reply-To', size=64),
'act_remind_attach': fields.boolean('Remind with attachment', help="Check this if\
you want that all documents attached to the object be attached \
to the reminder email sent."),
'act_mail_to_user': fields.boolean('Mail to responsible',help="Check this if \
you want the rule to send an email to the responsible person."),
'act_mail_to_watchers': fields.boolean('Mail to watchers (CC)',help="Check this\
if you want the rule to mark CC(mail to any other person\
defined in actions)."),
'act_mail_to_email': fields.char('Mail to these emails', size=128,help="Email-id \
of the persons whom mail is to be sent"),
'act_mail_body': fields.text('Mail body',help="Content of mail"),
'regex_name': fields.char('Regular Expression on Model Name', size=128),
'server_action_id': fields.many2one('ir.actions.server','Server Action',help="Describes the\
action name." \
"eg:on which object which action to be taken on basis of which condition"),
}
_defaults = {
'active': lambda *a: 1,
'trg_date_type': lambda *a: 'none',
'trg_date_range_type': lambda *a: 'day',
'act_mail_to_user': lambda *a: 0,
'act_remind_partner': lambda *a: 0,
'act_remind_user': lambda *a: 0,
'act_mail_to_watchers': lambda *a: 0,
}
_order = 'sequence'
def _check_mail(self, cr, uid, ids, context=None):
""" Check Mail
@ -390,9 +354,9 @@ class base_action_rule_line(osv.osv):
return True
_constraints = [
(_check_mail, 'Error: The mail is not well formated', ['act_mail_body']),
(_check_mail, 'Error: The mail is not well formated', ['act_mail_body']),
]
base_action_rule_line()
base_action_rule()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -16,52 +16,9 @@
<form string="Action Rule">
<field name="name" select="1"/>
<field name="active"/>
<field name="sequence"/>
<field name="max_level" />
<separator colspan="4" string="Rule lines"/>
<field name="rule_lines" colspan="4" nolabel="1"/>
</form>
</field>
</record>
<!-- Action Rule Tree View -->
<record id="view_base_action_rule_tree" model="ir.ui.view">
<field name="name">base.action.rule.tree</field>
<field name="model">base.action.rule</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Action Rule">
<field name="name" colspan="4"/>
<field name="max_level"/>
<field name="rule_lines" colspan="4" nolabel="1"/>
</tree>
</field>
</record>
<!-- Action Rule Action -->
<record id="base_action_rule_act" model="ir.actions.act_window">
<field name="name">Action Rules</field>
<field name="res_model">base.action.rule</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_base_action_rule_tree"/>
</record>
<menuitem id="menu_base_action_rule_form"
parent="menu_base_action_rule" action="base_action_rule_act" />
<!--
Action Rule Lines Form View
-->
<record id="view_base_action_rule_line_form" model="ir.ui.view">
<field name="name">base.action.rule.line.form</field>
<field name="model">base.action.rule.line</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Action Rule Line">
<field name="name" select="1"/>
<field name="active"/>
<field name="domain" colspan="4"/>
<notebook colspan="4">
<page string="Conditions">
<group col="2" colspan="2" name="model">
@ -79,45 +36,46 @@
<field name="trg_state_from"/>
<field name="trg_state_to"/>
</group>
<group col="2" colspan="2">
<separator colspan="4" string="Conditions on Priority Range"/>
<field name="trg_priority_from"/>
<field name="trg_priority_to"/>
</group>
<group col="2" colspan="2">
<separator colspan="4" string="Conditions on Timing"/>
<field name="trg_date_type"/>
<label align="1.0" string="Delay After Trigger Date:"/>
<group col="2" colspan="1">
<field name="trg_date_range" nolabel="1"/>
<group col="3" colspan="2" attrs="{'invisible': [('trg_date_type', '=', 'none')]}">
<field name="trg_date_range" string="Delay After Trigger Date"/>
<field name="trg_date_range_type" nolabel="1"/>
</group>
</group>
</group>
<separator colspan="4" string="Note"/>
<label align="0.0" string="The rule use a AND operator. The model must match all non empty fields so that the rule execute the action described in the 'Actions' tab." colspan="4"/>
<label align="0.0" colspan="4" width="900"
string="The rule use a AND operator. The model must match all non empty fields so that the rule execute the action described in the 'Actions' tab." />
</page>
<page string="Actions">
<separator colspan="4" string="Fields to Change"/>
<field name="act_user_id"/>
<field name="act_state"/>
<field name="act_priority"/>
<separator colspan="4" string="E-Mail Reminders (includes the content of the object)"/>
<field name="act_remind_partner"/>
<field name="act_remind_attach"/>
<field name="act_remind_user"/>
<group col="2" colspan="2" attrs="{'invisible': [('act_remind_user','=',False)]}">
<field name="act_reply_to" attrs="{'required':[('act_remind_user','=',True)]}"/>
</group>
<field colspan="4" name="act_email_cc"/>
<separator colspan="4" string="Server Action to be Triggered"/>
<field name="server_action_id"/>
<field name="filter_id"/>
</page>
<page string="E-Mail Actions">
<separator colspan="4" string="Template of Email to Send"/>
<field name="act_mail_to_user"/>
<field name="act_mail_to_watchers"/>
<field colspan="4" name="act_mail_to_email"/>
<field colspan="4" name="act_mail_body" attrs="{'required':[('act_remind_user','=',True)]}"/>
<!-- <group col="4" colspan="2">-->
<separator colspan="4" string="Template of Email to Send"/>
<field name="act_mail_to_watchers"/>
<field name="act_mail_to_user"/>
<field colspan="4" name="act_mail_to_email"/>
<!-- </group>-->
<!-- <group col="4" colspan="2">-->
<separator colspan="4" string="E-Mail Reminders (includes the content of the object)"/>
<field name="act_remind_partner"/>
<field name="act_remind_attach"/>
<field name="act_remind_user"/>
<group col="2" colspan="2" attrs="{'invisible': [('act_remind_user','=',False)]}">
<field name="act_reply_to" attrs="{'required':[('act_remind_user','=',True)]}"/>
</group>
<field colspan="4" name="act_email_cc"/>
<!-- </group>-->
<separator colspan="4" string="Mail Body"/>
<field colspan="4" name="act_mail_body" height="250"
nolabel="1" attrs="{'required':[('act_remind_user','=',True)]}" />
<separator colspan="4" string="Special Keywords to Be Used in The Body"/>
<label align="0.0" string="%%(object_id)s = Object ID" colspan="2"/>
<label align="0.0" string="%%(object_subject)s = Object subject" colspan="2"/>
@ -128,34 +86,41 @@
<label align="0.0" string="%%(object_user)s = Responsible name" colspan="2"/>
<label align="0.0" string="%%(object_user_email)s = Responsible email" colspan="2"/>
<label align="0.0" string="%%(object_user_phone)s = Responsible phone" colspan="2"/>
<label align="0.0" string="%% = The &apos;%%&apos; Character" colspan="2"/>
</page>
</notebook>
</form>
</field>
</record>
</record>
<!-- Action Rule Lines Tree View -->
<!-- Action Rule Tree View -->
<record id="view_base_action_rule_line_tree" model="ir.ui.view">
<field name="name">base.action.rule.line.tree</field>
<field name="model">base.action.rule.line</field>
<record id="view_base_action_rule_tree" model="ir.ui.view">
<field name="name">base.action.rule.tree</field>
<field name="model">base.action.rule</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Action Rule Lines">
<field name="name"/>
<field name="active"/>
<tree string="Action Rule">
<field name="name" colspan="4"/>
<field name="sequence"/>
<field name="max_level"/>
<field name="domain"/>
</tree>
</field>
</record>
<!-- Action Rule Action -->
<record id="base_action_rule_act" model="ir.actions.act_window">
<field name="name">Action Rules</field>
<field name="res_model">base.action.rule</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_base_action_rule_tree"/>
</record>
<act_window
domain="[('rule_id', '=', active_id)]"
id="act_rule_lines_open"
name="Open Rule Lines"
context="{'rule_id': active_id}"
res_model="base.action.rule.line"
src_model="base.action.rule"/>
<menuitem id="menu_base_action_rule_form"
parent="menu_base_action_rule" action="base_action_rule_act" />
</data>
</openerp>

View File

@ -1,3 +1,2 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_base_action_rule","base.action.rule","model_base_action_rule",,1,1,1,1
"access_base_action_rule_line","base.action.rule.line","model_base_action_rule_line",,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_base_action_rule base.action.rule model_base_action_rule 1 1 1 1
access_base_action_rule_line base.action.rule.line model_base_action_rule_line 1 1 1 1

View File

@ -19,7 +19,7 @@
#
##############################################################################
from datetime import datetime, timedelta
from datetime import datetime, timedelta, date
from dateutil import parser
from dateutil import rrule
from osv import fields, osv
@ -36,7 +36,7 @@ months = {
10: "October", 11: "November", 12: "December"
}
def get_recurrent_dates(rrulestring, exdate, startdate=None):
def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None):
"""
Get recurrent dates based on Rule string considering exdate and start date
@param rrulestring: Rulestring
@ -56,6 +56,8 @@ def get_recurrent_dates(rrulestring, exdate, startdate=None):
for date in exdate:
datetime_obj = todate(date)
rset1._exdate.append(datetime_obj)
if exrule:
rset1.exrule(rrule.rrulestr(str(exrule), dtstart=startdate))
re_dates = map(lambda x:x.strftime('%Y-%m-%d %H:%M:%S'), rset1._iter())
return re_dates
@ -176,28 +178,12 @@ html_invitation = """
</div>
</td>
</tr>
<tr valign="top">
<td><b>Are you coming?</b></td>
<td><b>:</b></td>
<td colspan="3">
<UL>
<LI>YES</LI>
<LI>NO</LI>
<LI>MAYBE</LI>
</UL>
</td>
</tr>
</table>
</td>
</tr>
</table>
<table border="0" cellspacing="10" cellpadding="0" width="100%%"
style="font-family: Arial, Sans-serif; font-size: 14">
<tr>
<td width="100%%"><b>Note:</b> If you are interested please reply this
mail and keep only your response from options <i>YES, NO</i>
and <i>MAYBE</i>.</td>
</tr>
<tr>
<td width="100%%">From:</td>
</tr>
@ -257,22 +243,22 @@ class calendar_attendee(osv.osv):
attdata.sent_by_uid.address_id.email)
if name == 'cn':
if attdata.user_id:
result[id][name] = self._get_address(attdata.user_id.name, attdata.email)
result[id][name] = attdata.user_id.name
elif attdata.partner_address_id:
result[id][name] = self._get_address(attdata.partner_id.name, attdata.email)
result[id][name] = attdata.partner_address_id.name or attdata.partner_id.name
else:
result[id][name] = self._get_address(None, attdata.email)
result[id][name] = attdata.email or ''
if name == 'delegated_to':
todata = []
for parent in attdata.parent_ids:
if parent.email:
todata.append('MAILTO:' + parent.email)
for child in attdata.child_ids:
if child.email:
todata.append('MAILTO:' + child.email)
result[id][name] = ', '.join(todata)
if name == 'delegated_from':
fromdata = []
for child in attdata.child_ids:
if child.email:
fromdata.append('MAILTO:' + child.email)
for parent in attdata.parent_ids:
if parent.email:
fromdata.append('MAILTO:' + parent.email)
result[id][name] = ', '.join(fromdata)
if name == 'event_date':
if attdata.ref:
@ -383,49 +369,72 @@ property or property parameter."),
'state': lambda *x: 'needs-action',
}
response_re = re.compile("Are you coming\?.*\n*.*(YES|NO|MAYBE).*", re.UNICODE)
def msg_new(self, cr, uid, msg):
"""
def get_ics_file(self, cr, uid, event_obj, context=None):
"""
Returns iCalendar file for the event invitation
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
"""
return False
def msg_act_get(self, msg):
"""
Get Message.
@param self: The object pointer
@return: dictionary of actions which contain state field value.
"""
mailgate_obj = self.pool.get('mail.gateway')
body = mailgate_obj.msg_body_get(msg)
actions = {}
res = self.response_re.findall(body['body'])
if res:
actions['state'] = res[0]
return actions
def msg_update(self, cr, uid, ids, msg, data={}, default_act='None'):
"""
Update msg state which may be accepted.declined.tentative.
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of calendar attendees IDs.
@param event_obj: Event object (browse record)
@param context: A standard dictionary for contextual values
@return: True
@return: .ics file content
"""
msg_actions = self.msg_act_get(msg)
if msg_actions.get('state'):
if msg_actions['state'] in ['YES', 'NO', 'MAYBE']:
mapping = {'YES': 'accepted', 'NO': 'declined', 'MAYBE': 'tentative'}
status = mapping[msg_actions['state']]
print 'Got response for invitation id: %s as %s' % (ids, status)
self.write(cr, uid, ids, {'state': status})
return True
res = None
def ics_datetime(idate, short=False):
if short:
return date.fromtimestamp(time.mktime(time.strptime(idate, '%Y-%m-%d')))
else:
return datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
try:
import vobject
except ImportError:
return res
cal = vobject.iCalendar()
event = cal.add('vevent')
event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
event.add('dtstart').value = ics_datetime(event_obj.date)
event.add('dtend').value = ics_datetime(event_obj.date_deadline)
event.add('summary').value = event_obj.name
if event_obj.description:
event.add('description').value = event_obj.description
if event_obj.location:
event.add('location').value = event_obj.location
if event_obj.rrule:
event.add('rrule').value = event_obj.rrule
if event_obj.alarm_id:
# computes alarm data
valarm = event.add('valarm')
alarm_object = self.pool.get('res.alarm')
alarm_data = alarm_object.read(cr, uid, event_obj.alarm_id.id, context=context)
# Compute trigger data
interval = alarm_data['trigger_interval']
occurs = alarm_data['trigger_occurs']
duration = (occurs == 'after' and alarm_data['trigger_duration']) \
or -(alarm_data['trigger_duration'])
related = alarm_data['trigger_related']
trigger = valarm.add('TRIGGER')
trigger.params['related'] = [related.upper()]
if interval == 'days':
delta = timedelta(days=duration)
if interval == 'hours':
delta = timedelta(hours=duration)
if interval == 'minutes':
delta = timedelta(minutes=duration)
trigger.value = delta
# Compute other details
valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
for attendee in event_obj.attendee_ids:
attendee_add = event.add('attendee')
attendee_add.params['CUTYPE'] = [str(attendee.cutype)]
attendee_add.params['ROLE'] = [str(attendee.role)]
attendee_add.params['RSVP'] = [str(attendee.rsvp)]
attendee_add.value = 'MAILTO:' + attendee.email
res = cal.serialize()
return res
def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context={}):
"""
Send mail for calendar attendee.
@ -443,7 +452,7 @@ property or property parameter."),
res_obj = att.ref
sub = '[%s Invitation][%d] %s' % (company, att.id, res_obj.name)
att_infos = []
other_invitaion_ids = self.search(cr, uid, [('ref', '=', att.ref._name + ',' + str(att.ref.id))])
other_invitaion_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
for att2 in self.browse(cr, uid, other_invitaion_ids):
att_infos.append(((att2.user_id and att2.user_id.name) or \
(att2.partner_id and att2.partner_id.name) or \
@ -459,15 +468,17 @@ property or property parameter."),
'company': company
}
body = html_invitation % body_vals
attach = self.get_ics_file(cr, uid, res_obj, context=context)
if mail_to and email_from:
tools.email_send(
email_from,
mail_to,
sub,
body,
subtype='html',
reply_to=email_from
)
email_from,
mail_to,
sub,
body,
attach=attach and [('invitation.ics', attach)] or None,
subtype='html',
reply_to=email_from
)
return True
def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
@ -569,7 +580,7 @@ class res_alarm(osv.osv):
are both optional, but if one occurs, so MUST the other"""),
'repeat': fields.integer('Repeat'),
'active': fields.boolean('Active', help="If the active field is set to \
true, it will allow you to hide the event alarm information without removing it.")
true, it will allow you to hide the event alarm information without removing it.")
}
_defaults = {
'trigger_interval': lambda *x: 'minutes',
@ -659,41 +670,41 @@ class calendar_alarm(osv.osv):
__attribute__ = {}
_columns = {
'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
'name': fields.char('Summary', size=124, help="""Contains the text to be \
used as the message subject for email \
or contains the text to be used for display"""),
'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
required=True, help="Defines the action to be invoked when an alarm is triggered"),
'description': fields.text('Description', help='Provides a more complete \
description of the calendar component, than that \
provided by the "SUMMARY" property'),
'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
'alarm_id', 'attendee_id', 'Attendees', readonly=True),
'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
which is rendered when the alarm is triggered for audio,
* File which is intended to be sent as message attachments for email,
* Points to a procedure resource, which is invoked when\
the alarm is triggered for procedure."""),
'res_id': fields.integer('Resource ID'),
'model_id': fields.many2one('ir.model', 'Model'),
'user_id': fields.many2one('res.users', 'Owner'),
'event_date': fields.datetime('Event Date'),
'event_end_date': fields.datetime('Event End Date'),
'trigger_date': fields.datetime('Trigger Date', readonly="True"),
'state':fields.selection([('draft', 'Draft'),
('run', 'Run'),
('stop', 'Stop'),
('done', 'Done'),
], 'State', select=True, readonly=True),
}
'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
'name': fields.char('Summary', size=124, help="""Contains the text to be \
used as the message subject for email \
or contains the text to be used for display"""),
'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
required=True, help="Defines the action to be invoked when an alarm is triggered"),
'description': fields.text('Description', help='Provides a more complete \
description of the calendar component, than that \
provided by the "SUMMARY" property'),
'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
'alarm_id', 'attendee_id', 'Attendees', readonly=True),
'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
which is rendered when the alarm is triggered for audio,
* File which is intended to be sent as message attachments for email,
* Points to a procedure resource, which is invoked when\
the alarm is triggered for procedure."""),
'res_id': fields.integer('Resource ID'),
'model_id': fields.many2one('ir.model', 'Model'),
'user_id': fields.many2one('res.users', 'Owner'),
'event_date': fields.datetime('Event Date'),
'event_end_date': fields.datetime('Event End Date'),
'trigger_date': fields.datetime('Trigger Date', readonly="True"),
'state':fields.selection([
('draft', 'Draft'),
('run', 'Run'),
('stop', 'Stop'),
('done', 'Done'),
], 'State', select=True, readonly=True),
}
_defaults = {
'action': lambda *x: 'email',
'state': lambda *x: 'run',
}
'action': lambda *x: 'email',
'state': lambda *x: 'run',
}
def create(self, cr, uid, vals, context={}):
"""
create new record.
@ -983,7 +994,7 @@ class calendar_event(osv.osv):
'exdate': fields.text('Exception Date/Times', help="This property \
defines the list of date/time exceptions for arecurring calendar component."),
'exrule': fields.char('Exception Rule', size=352, help="defines a \
rule or repeating pattern for anexception to a recurrence set"),
rule or repeating pattern for an exception to a recurrence set"),
'rrule': fields.function(_get_rulestring, type='char', size=124, method=True, \
string='Recurrent Rule', store=True, \
fnct_inv=_set_rrulestring, help='Defines a\
@ -1031,7 +1042,9 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
'end_date': fields.date('Repeat Until'),
'attendee_ids': fields.many2many('calendar.attendee', 'event_attendee_rel',\
'event_id', 'attendee_id', 'Attendees'),
'allday': fields.boolean('All Day')
'allday': fields.boolean('All Day'),
'active': fields.boolean('Active', help="If the active field is set to \
true, it will allow you to hide the event alarm information without removing it.")
}
_defaults = {
@ -1040,6 +1053,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
'freq': lambda *x: 'None',
'select1': lambda *x: 'date',
'interval': lambda *x: 1,
'active': lambda *x: 1,
}
def open_event(self, cr, uid, ids, context=None):
@ -1164,8 +1178,10 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
if count > limit:
break
event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
if start_date and start_date <= event_date:
start_date = event_date
# To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
# if start_date and start_date <= event_date:
# start_date = event_date
start_date = event_date
if not data['rrule']:
if start_date and (event_date < start_date):
continue
@ -1213,7 +1229,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
if isinstance(select, (str, int, long)):
return ids and ids[0] or False
return ids
def compute_rule_string(self, cr, uid, datas, context=None, *args):
"""
Compute rule string.
@ -1332,6 +1348,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
event_id = base_calendar_id2real_id(event_id)
if not event_id in new_ids:
new_ids.append(event_id)
res = super(calendar_event, self).write(cr, uid, new_ids, vals, context=context)
if vals.has_key('alarm_id') or vals.has_key('base_calendar_alarm_id'):
alarm_obj = self.pool.get('res.alarm')

View File

@ -8,8 +8,13 @@
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Invitation details">
<field name="email" string="Invitation To"/>
<field name="sent_by_uid" string="Invitation From" />
<group col="6" colspan="4">
<field name="email" string="Invitation To"/>
<field name="cutype" string="Invitation type" />
<field name="rsvp" />
<field name="role" string="Role" />
<field name="sent_by_uid" string="Invitation From" />
</group>
<notebook colspan="4">
<page string="Invitation">
<separator string="Invitation Detail" colspan="4" />
@ -20,9 +25,6 @@
string="Partner Contact" />
<field name="partner_id"
string="Partner" readonly="1" />
<field name="role" string="Role" />
<field name="cutype" string="Invitation type" />
<field name="rsvp" />
</group>
<separator string="Event Detail" colspan="4" />
<group colspan="4" col="4">
@ -181,7 +183,7 @@
<field name="model">calendar.event</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Meetings">
<form string="Events">
<group col="6" colspan="4">
<field name="name" select="1" string="Summary"
colspan="4" />
@ -190,7 +192,7 @@
<field name="date" string="Start Date" required="1" select="1"
on_change="onchange_dates(date,duration,False,allday)" />
<field name="duration" widget="float_time"
on_change="onchange_dates(date,duration,False,allday)" attrs="{'readonly': [('allday', '=', True)]}"/>
on_change="onchange_dates(date,duration,False,allday)" attrs="{'invisible': [('allday', '=', True)]}"/>
<field name="date_deadline" string="End Date" required="1"
on_change="onchange_dates(date,False,date_deadline)" />
<field name="location" />
@ -257,7 +259,6 @@
<field name="user_id" string="Responsible User" />
<field name="show_as" string="Show time as"/>
<field name="class" string="Privacy"/>
<field name="rrule" groups="base.group_extended" />
<field name="recurrent_id" invisible="1" />
<field name="recurrent_uid" invisible="1" />
</group>
@ -333,6 +334,7 @@
<field name="location" />
<field name="show_as" />
<field name="class" string="Privacy" />
<field name="user_id" invisible="1"/>
</tree>
</field>
</record>
@ -353,6 +355,44 @@
</field>
</record>
<!-- Event Search View-->
<record id="view_calendar_event_filter" model="ir.ui.view">
<field name="name">Calendar Events Search</field>
<field name="model">calendar.event</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Search Events">
<group col="12" colspan="4">
<filter icon="terp-partner" string="My Events"
domain="[('user_id','=',uid)]"
help="My Events" />
<separator orientation="vertical"/>
<field name="name" select="1" string="Summary"/>
<field name="location" select="1" string="Location"/>
<separator orientation="vertical"/>
<field name="user_id" select="1" widget="selection"/>
<separator orientation="vertical"/>
<field name="show_as" select="1"/>
<field name="class" select="1"/>
</group>
<newline/>
<group expand="0" string="Group By..." colspan="16">
<filter string="Date" icon="terp-project"
domain="[]" context="{'group_by':'date'}" />
<filter string="Availability" icon="terp-project"
domain="[]" context="{'group_by':'show_as'}" />
<filter string="Privacy" icon="terp-crm"
domain="[]" context="{'group_by':'class'}" />
<separator orientation="vertical" />
<filter string="Responsible" icon="terp-crm" domain="[]"
context="{'group_by':'user_id'}" />
</group>
</search>
</field>
</record>
<!-- Event action -->
<record id="action_view_event" model="ir.actions.act_window">
@ -361,6 +401,7 @@
<field name="res_model">calendar.event</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar</field>
<field name="search_view_id" ref="view_calendar_event_filter"/>
</record>
<!-- Event menu -->

View File

@ -44,7 +44,7 @@ class base_calendar_invite_attendee(osv.osv_memory):
'contact_ids': fields.many2many('res.partner.address', 'invite_contact_rel',
'invite_id', 'contact_id', 'Contacts'),
'send_mail': fields.boolean('Send mail?', help='Check this if you want to \
send an Email to Invited Person')
send an Email to Invited Person')
}
_defaults = {
@ -66,20 +66,19 @@ class base_calendar_invite_attendee(osv.osv_memory):
model = False
model_field = False
context_id = context and context.get('active_id', False) or False
if not context or not context.get('model'):
return {}
else:
model = context.get('model')
model_field = context.get('attendee_field', False)
obj = self.pool.get(model)
res_obj = obj.browse(cr, uid, context_id)
att_obj = self.pool.get('calendar.attendee')
for datas in self.read(cr, uid, ids, context=context):
obj = self.pool.get(model)
res_obj = obj.browse(cr, uid, context_id)
type = datas.get('type')
att_obj = self.pool.get('calendar.attendee')
vals = []
mail_to = []
attendees = []
@ -123,15 +122,13 @@ class base_calendar_invite_attendee(osv.osv_memory):
if contact.email:
mail_to.append(contact.email)
att = att_obj.browse(cr, uid, context_id)
for att_val in vals:
if model == 'calendar.attendee':
if ref:
att_val.update({
'parent_ids': [(4, att.id)],
'ref': att.ref._name + ',' +str(att.ref.id)
})
att = att_obj.browse(cr, uid, context_id)
att_val.update({
'parent_ids': [(4, att.id)],
'ref': att.ref._name + ',' +str(att.ref.id)
})
attendees.append(att_obj.create(cr, uid, att_val))
if model_field:
for attendee in attendees:

View File

@ -17,33 +17,34 @@
<attribute name='string'>Configure Your Company</attribute>
</xpath>
<xpath expr='//separator[@string="vsep"]' position='attributes'>
<attribute name='rowspan'>23</attribute>
<attribute name='rowspan'>25</attribute>
<attribute name='string'></attribute>
</xpath>
<group string="res_config_contents" position="replace">
<group colspan="5">
<group colspan="2">
<field name="company_id" invisible="1"/>
<field name="name" colspan="4" required="True"/>
<newline/>
<field name="street"/>
<field name="street2"/>
<field name="name" required="True"/><field name="account_no"/>
<field name="currency" widget="selection"/><field name="street"/>
<field name="zip"/>
<field name="street2"/>
<field name="city"/>
<field name="country_id"/>
<field name="state_id"/>
<field name="email"/>
<field name="phone"/>
<field name="currency" widget="selection"/>
<field name="website"/>
</group>
<newline/>
<group colspan="2" groups="base.group_extended">
<separator string="Report Information" colspan="4"/>
<field name="rml_header1" colspan="4"/>
<field name="rml_footer1" colspan="4"/>
<field name="rml_footer2" colspan="4"/>
<field name="rml_header1" colspan="5"/>
<field name="rml_footer1" colspan="5"/>
<field name="rml_footer2" colspan="5"/>
<separator colspan="4"
string="Your Logo - Use a size of about 450x150 pixels."/>
<field colspan="4" name="logo" widget="image"
<field colspan="5" name="logo" widget="image"
nolabel="1"/>
</group>
</group>

View File

@ -70,7 +70,8 @@ class base_gtkcontactform(osv.osv_memory):
'training':fields.boolean('OpenERP Training Program'),
'other':fields.boolean('Other'),
'ebook':fields.boolean('ebook'),
'updates':fields.boolean('updates'),
'updates':fields.boolean('Updates'),
'contact_me':fields.boolean('Contact Me'),
}
def execute(self, cr, uid, ids, context=None):
company_id = self.pool.get('base.setup.company').search(cr, uid, [])

View File

@ -14,7 +14,7 @@
<attribute name="string">Information about you and the company you are related too. The Basic information related to the company has already been collected and some more information required by you to fill.</attribute>
</xpath>
<xpath expr='//separator[@string="title"]' position='attributes'>
<attribute name='string'>Do You Need Them ?</attribute>
<attribute name='string'>Would you like more information or documentation ?</attribute>
<attribute name='colspan'>4</attribute>
</xpath>
<xpath expr='//separator[@string="vsep"]' position='attributes'>
@ -24,6 +24,8 @@
<group string="res_config_contents" position="replace">
<group colspan="4" height="450" width="600">
<group colspan="4">
<field name="contact_me" nolabel="1"/>
<label align="0.0" string="I accept to be contacted by OpenERP." colspan="3"/>
<field name="ebook" nolabel="1"/>
<label align="0.0" string="I want to receive the Open ERP ebook (PDF) by email." colspan="3"/>
<field name="updates" nolabel="1"/>
@ -71,7 +73,7 @@
<record id="base_setup_contact_todo" model="ir.actions.todo">
<field name="action_id" ref="action_base_contact"/>
<field name="sequence">1</field>
<field name="restart">onskip</field>
<field name="restart">never</field>
</record>
</data>
</openerp>

View File

@ -73,6 +73,7 @@ class base_setup_installer(osv.osv_memory):
'report_designer':fields.boolean('Advanced Reporting',
help="Lets you install various tools to simplify and enhance "
"OpenERP's report creation."),
'thunderbird' :fields.boolean('Thunderbird'),
# Vertical modules
'profile_association':fields.boolean('Associations',
help="Installs a preselected set of OpenERP "

View File

@ -86,8 +86,8 @@ class base_setup_company(osv.osv_memory):
'street2':fields.char('Street 2', size=128),
'zip':fields.char('Zip Code', size=24),
'city':fields.char('City', size=128),
'state_id':fields.selection(_get_all_states, 'States'),
'country_id':fields.selection(_get_all_countries, 'Countries'),
'state_id':fields.selection(_get_all_states, 'State'),
'country_id':fields.selection(_get_all_countries, 'Country'),
'email':fields.char('E-mail', size=64),
'phone':fields.char('Phone', size=64),
'currency':fields.many2one('res.currency', 'Currency', required=True),
@ -104,6 +104,8 @@ Web: http://openerp.com - Fax: +32.81.73.35.01 - Fortis Bank: 126-2013269-07''')
We suggest you to put bank information here:
IBAN: BE74 1262 0121 6907 - SWIFT: CPDF BE71 - VAT: BE0477.472.701'''),
'logo':fields.binary('Logo'),
'account_no':fields.char('Account No', size=64),
'website': fields.char('Web', size=64),
}
def execute(self, cr, uid, ids, context=None):
@ -120,10 +122,12 @@ IBAN: BE74 1262 0121 6907 - SWIFT: CPDF BE71 - VAT: BE0477.472.701'''),
'rml_footer2':payload.rml_footer2,
'logo':payload.logo,
'currency_id':payload.currency.id,
'account_no':payload.account_no,
})
company.partner_id.write({
'name':payload.name,
'website':payload.website,
})
address_data = {

View File

@ -7,13 +7,13 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2009-08-28 16:01+0000\n"
"PO-Revision-Date: 2009-12-29 16:54+0000\n"
"Last-Translator: Carlo Vettore <Unknown>\n"
"PO-Revision-Date: 2010-05-13 13:58+0000\n"
"Last-Translator: eLBati - albatos.com <lorenzo.battistini@albatos.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2010-04-17 04:03+0000\n"
"X-Launchpad-Export-Date: 2010-05-14 04:55+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#. module: base_vat
@ -31,9 +31,9 @@ msgstr "XML non valido per Visualizzazione Architettura!"
#. module: base_vat
#: field:res.partner,vat_subjected:0
msgid "VAT Legal Statement"
msgstr "Numero di Partita IVA"
msgstr "Soggetto a IVA"
#. module: base_vat
#: model:ir.module.module,shortdesc:base_vat.module_meta_information
msgid "VAT"
msgstr "P.IVA"
msgstr "Partita IVA"

View File

@ -18,7 +18,7 @@
<record model="ir.ui.view" id="view_crm_opportunity_categ_graph">
<field name="name">report.crm.opportunity.categ.graph</field>
<field name="model">crm.opportunity.report</field>
<field name="model">crm.lead.report</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<graph string="Opportunity by Sales Team and Categories" type="bar" orientation="horizontal">
@ -30,7 +30,7 @@
<act_window name="Opportunities By Categories"
domain="[('state','!=','done'),('state','!=','cancel')]"
res_model="crm.opportunity.report"
res_model="crm.lead.report"
view_type="form"
view_mode="graph,tree,form"
view_id="view_crm_opportunity_categ_graph"

View File

@ -1,88 +1,77 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="board.note.type" id="note_crm_type">
<field name="name">CRM Configuration</field>
</record>
<record model="ir.ui.view" id="view_crm_opportunity_user_graph1">
<field name="name">crm.opportunity.user.graph1</field>
<field name="model">crm.opportunity.report</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<graph string="Opportunities by User and Sales Team" type="pie">
<field name="state"/>
<field name="nbr" operator="+"/>
</graph>
</field>
</record>
<record model="ir.actions.act_window" id="act_my_leads">
<field name="res_model">crm.lead</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="crm.crm_case_tree_view_leads"/>
<data>
<record model="board.note.type" id="note_crm_type">
<field name="name">CRM Configuration</field>
</record>
<record model="ir.ui.view" id="view_crm_opportunity_user_graph1">
<field name="name">crm.opportunity.user.graph1</field>
<field name="model">crm.lead.report</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<graph string="Opportunities by User and Section" type="pie">
<field name="state"/>
<field name="nbr" operator="+"/>
</graph>
</field>
</record>
<record model="ir.actions.act_window" id="act_my_leads">
<field name="res_model">crm.lead</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="crm.crm_case_tree_view_leads"/>
<field name="domain">[('user_id','=',uid),('state','!=','pending'),('state','!=','cancel')]</field>
</record>
<record model="ir.actions.act_window" id="act_my_meetings">
<field name="res_model">crm.meeting</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="crm.crm_case_tree_view_meet"/>
</record>
<record model="ir.actions.act_window" id="act_my_meetings">
<field name="res_model">crm.meeting</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="crm.crm_case_tree_view_meet"/>
<field name="domain">[('user_id','=',uid),('state','!=','pending'),('state','!=','cancel')]</field>
</record>
<record model="ir.actions.act_window" id="act_my_leads_stage">
<field name="res_model">crm.lead.report</field>
<field name="view_type">form</field>
<field name="view_mode">graph,tree,form</field>
<field name="view_id" ref="view_crm_lead_stage_graph1"/>
<field name="domain">[('user_id','=',uid),('state','!=','pending'),('state','!=','cancel')]</field>
</record>
<act_window name="My Cases"
domain="[('user_id','=',uid),('state','!=','pending'),('state','!=','cancel')]"
res_model="crm.case"
view_type="form"
view_mode="tree,form"
id="act_my_cases"/>
<record model="ir.actions.act_window" id="act_my_leads_stage">
<field name="res_model">crm.lead.report</field>
<field name="view_type">form</field>
<field name="view_mode">graph,tree,form</field>
<field name="view_id" ref="view_crm_lead_stage_graph1"/>
<field name="domain">[('user_id','=',uid),('state','!=','pending'),('state','!=','cancel')]</field>
</record>
</record>
<record model="ir.actions.act_window" id="act_sales_pipeline">
<field name="res_model">crm.opportunity.report</field>
<field name="view_type">form</field>
<field name="view_mode">graph,tree,form</field>
<field name="view_id" ref="view_crm_opportunity_user_graph1"/>
<field name="domain">[('user_id','=',uid),('state','!=','pending'),('state','!=','cancel')]</field>
</record>
<record model="ir.actions.act_window" id="act_sales_pipeline">
<field name="res_model">crm.lead.report</field>
<field name="view_type">form</field>
<field name="view_mode">graph,tree,form</field>
<field name="view_id" ref="view_crm_opportunity_user_graph1"/>
<field name="domain">[('user_id','=',uid),('state','!=','pending'),('state','!=','cancel'),('type', '=', 'opportunity')]</field>
</record>
<record model="ir.ui.view" id="board_crm_form">
<field name="name">CRM - Dashboard Form</field>
<field name="model">board.board</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Connecting Dashboard">
<hpaned>
<record model="ir.ui.view" id="board_crm_form">
<field name="name">CRM - Dashboard Form</field>
<field name="model">board.board</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Connecting Dashboard">
<hpaned>
<child1>
<child1>
<action
name="%(act_my_leads)d"
colspan="4"
height="150"
width="510"/>
<action
name="%(act_my_leads)d"
colspan="4"
height="150"
width="510"/>
<action
string="My Meetings"
name="%(act_my_meetings)d"
height="150"
colspan="4"/>
<action
string="My Meetings"
name="%(act_my_meetings)d"
height="150"
colspan="4"/>
<action
string="My Cases"
name="%(act_my_cases)d"
colspan="4"
height="200"/>
</child1>
</child1>
<child2>
<action
<child2>
<action
string="My Leads By Stage"
name="%(act_my_leads_stage)d"
colspan="4"/>

View File

@ -1,101 +0,0 @@
#!/usr/bin/env python
"""
Buffering HTTP Server
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from utils import VERSION, AUTHOR
__version__ = VERSION
__author__ = AUTHOR
from BaseHTTPServer import BaseHTTPRequestHandler
import os
class BufferedHTTPRequestHandler(BaseHTTPRequestHandler):
"""
Buffering HTTP Request Handler
This class is an extension to the BaseHTTPRequestHandler
class which buffers the whole output and sends it at once
after the processing if the request is finished.
This makes it possible to work together with some clients
which otherwise would break (e.g. cadaver)
"""
def _init_buffer(self):
"""initialize the buffer.
If you override the handle() method remember to call
this (see below)
"""
self.__buffer = ""
self.__outfp = os.tmpfile()
def _append(self,s):
""" append a string to the buffer """
self.__buffer = self.__buffer+s
def _flush(self):
""" flush the buffer to wfile """
self.wfile.write(self.__buffer)
self.__outfp.write(self.__buffer)
self.__outfp.flush()
self.wfile.flush()
self.__buffer = ""
def handle(self):
""" Handle a HTTP request """
self._init_buffer()
BaseHTTPRequestHandler.handle(self)
self._flush()
def send_header(self, keyword, value):
"""Send a MIME header."""
if self.request_version != 'HTTP/0.9':
self._append("%s: %s\r\n" % (keyword, value))
def end_headers(self):
"""Send the blank line ending the MIME headers."""
if self.request_version != 'HTTP/0.9':
self._append("\r\n")
def send_response(self, code, message=None):
self.log_request(code)
if message is None:
if self.responses.has_key(code):
message = self.responses[code][0]
else:
message = ''
if self.request_version != 'HTTP/0.9':
self._append("%s %s %s\r\n" %
(self.protocol_version, str(code), message))
self.send_header('Server', self.version_string())
self.send_header('Connection', 'close')
self.send_header('Date', self.date_time_string())
protocol_version="HTTP/1.1"
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,381 +0,0 @@
#!/usr/bin/env python
"""
Python WebDAV Server.
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
This module builds on AuthServer by implementing the standard DAV
methods.
Subclass this class and specify an IFACE_CLASS. See example.
"""
DEBUG = None
from utils import VERSION, AUTHOR
__version__ = VERSION
__author__ = AUTHOR
from propfind import PROPFIND
from delete import DELETE
from davcopy import COPY
from davmove import MOVE
from string import atoi, split
from status import STATUS_CODES
from errors import *
import os
import sys
import time
import socket
import string
import posixpath
import base64
import urlparse
import urllib
import BaseHTTPServer
class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""Simple DAV request handler with
- GET
- HEAD
- PUT
- OPTIONS
- PROPFIND
- PROPPATCH
- MKCOL
It uses the resource/collection classes for serving and
storing content.
"""
server_version = "DAV/" + __version__
protocol_version = 'HTTP/1.1'
### utility functions
def _log(self, message):
pass
def _append(self, s):
""" write the string to wfile """
self.wfile.write(s)
def send_body(self, DATA, code, msg, desc, ctype='application/octet-stream', headers=None):
""" send a body in one part """
if not headers:
headers = {}
self.send_response(code, message=msg)
self.send_header("Connection", "keep-alive")
self.send_header("Accept-Ranges", "bytes")
for a, v in headers.items():
self.send_header(a, v)
if DATA:
self.send_header("Content-Length", str(len(DATA)))
self.send_header("Content-Type", ctype)
else:
self.send_header("Content-Length", "0")
self.end_headers()
if DATA:
self._append(DATA)
def send_body_chunks(self, DATA, code, msg, desc, ctype='text/xml; encoding="utf-8"'):
""" send a body in chunks """
self.responses[207]=(msg, desc)
self.send_response(code, message=msg)
self.send_header("Content-type", ctype)
self.send_header("Connection", "keep-alive")
self.send_header("Transfer-Encoding", "chunked")
self.end_headers()
self._append(hex(len(DATA))[2:]+"\r\n")
self._append(DATA)
self._append("\r\n")
self._append("0\r\n")
self._append("\r\n")
### HTTP METHODS
def do_OPTIONS(self):
"""return the list of capabilities """
self.send_response(200)
self.send_header("Allow", "GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE")
self.send_header("Content-Type", "text/plain")
self.send_header("Connection", "keep-alive")
self.send_header("DAV", "1")
self.end_headers()
def do_PROPFIND(self):
dc = self.IFACE_CLASS
# read the body
body = None
if self.headers.has_key("Content-Length"):
l = self.headers['Content-Length']
body = self.rfile.read(atoi(l))
alt_body = """<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:"><prop>
<getcontentlength xmlns="DAV:"/>
<getlastmodified xmlns="DAV:"/>
<getcreationdate xmlns="DAV:"/>
<checked-in xmlns="DAV:"/>
<executable xmlns="http://apache.org/dav/props/"/>
<displayname xmlns="DAV:"/>
<resourcetype xmlns="DAV:"/>
<checked-out xmlns="DAV:"/>
</prop></propfind>"""
#self.wfile.write(body)
# which Depth?
if self.headers.has_key('Depth'):
d = self.headers['Depth']
else:
d = "infinity"
uri = self.geturi()
pf = PROPFIND(uri, dc, d)
if body:
pf.read_propfind(body)
try:
DATA = pf.createResponse()
DATA = DATA+"\n"
# print "Data:", DATA
except DAV_NotFound, (ec, dd):
return self.send_notFound(dd, uri)
except DAV_Error, (ec, dd):
return self.send_error(ec, dd)
self.send_body_chunks(DATA, 207, "Multi-Status", "Multiple responses")
def geturi(self):
buri = self.IFACE_CLASS.baseuri
if buri[-1] == '/':
return urllib.unquote(buri[:-1]+self.path)
else:
return urllib.unquote(buri+self.path)
def do_GET(self):
"""Serve a GET request."""
dc = self.IFACE_CLASS
uri = self.geturi()
# get the last modified date
try:
lm = dc.get_prop(uri, "DAV:", "getlastmodified")
except:
lm = "Sun, 01 Dec 2014 00:00:00 GMT" # dummy!
headers = {"Last-Modified":lm , "Connection": "keep-alive"}
# get the content type
try:
ct = dc.get_prop(uri, "DAV:", "getcontenttype")
except:
ct = "application/octet-stream"
# get the data
try:
data = dc.get_data(uri)
except DAV_Error, (ec, dd):
self.send_status(ec)
return
# send the data
self.send_body(data, 200, "OK", "OK", ct, headers)
def do_HEAD(self):
""" Send a HEAD response """
dc = self.IFACE_CLASS
uri = self.geturi()
# get the last modified date
try:
lm = dc.get_prop(uri, "DAV:", "getlastmodified")
except:
lm = "Sun, 01 Dec 2014 00:00:00 GMT" # dummy!
headers = {"Last-Modified":lm, "Connection": "keep-alive"}
# get the content type
try:
ct = dc.get_prop(uri, "DAV:", "getcontenttype")
except:
ct = "application/octet-stream"
try:
data = dc.get_data(uri)
headers["Content-Length"] = str(len(data))
except DAV_NotFound:
self.send_body(None, 404, "Not Found", "")
return
self.send_body(None, 200, "OK", "OK", ct, headers)
def do_POST(self):
self.send_error(404, "File not found")
def do_MKCOL(self):
""" create a new collection """
dc = self.IFACE_CLASS
uri = self.geturi()
try:
res = dc.mkcol(uri)
if res:
self.send_body(None, 201, "Created", '')
else:
self.send_body(None, 415, "Cannot create", '')
#self.send_header("Connection", "keep-alive")
# Todo: some content, too
except DAV_Error, (ec, dd):
self.send_body(None, int(ec), dd, dd)
def do_DELETE(self):
""" delete an resource """
dc = self.IFACE_CLASS
uri = self.geturi()
dl = DELETE(uri, dc)
if dc.is_collection(uri):
res = dl.delcol()
else:
res = dl.delone()
if res:
self.send_status(207, body=res)
else:
self.send_status(204)
def do_PUT(self):
dc = self.IFACE_CLASS
# read the body
body = None
if self.headers.has_key("Content-Length"):
l = self.headers['Content-Length']
body = self.rfile.read(atoi(l))
uri = self.geturi()
ct = None
if self.headers.has_key("Content-Type"):
ct = self.headers['Content-Type']
try:
dc.put(uri, body, ct)
except DAV_Error, (ec, dd):
self.send_status(ec)
return
self.send_status(201)
def do_COPY(self):
""" copy one resource to another """
try:
self.copymove(COPY)
except DAV_Error, (ec, dd):
self.send_status(ec)
def do_MOVE(self):
""" move one resource to another """
try:
self.copymove(MOVE)
except DAV_Error, (ec, dd):
self.send_status(ec)
def copymove(self, CLASS):
""" common method for copying or moving objects """
dc = self.IFACE_CLASS
# get the source URI
source_uri = self.geturi()
# get the destination URI
dest_uri = self.headers['Destination']
dest_uri = urllib.unquote(dest_uri)
# Overwrite?
overwrite = 1
result_code = 204
if self.headers.has_key("Overwrite"):
if self.headers['Overwrite']=="F":
overwrite=None
result_code=201
# instanciate ACTION class
cp = CLASS(dc, source_uri, dest_uri, overwrite)
# Depth?
d = "infinity"
if self.headers.has_key("Depth"):
d = self.headers['Depth']
if d!="0" and d!="infinity":
self.send_status(400)
return
if d=="0":
res = cp.single_action()
self.send_status(res)
return
# now it only can be "infinity" but we nevertheless check for a collection
if dc.is_collection(source_uri):
try:
res = cp.tree_action()
except DAV_Error, (ec, dd):
self.send_status(ec)
return
else:
try:
res = cp.single_action()
except DAV_Error, (ec, dd):
self.send_status(ec)
return
if res:
self.send_body_chunks(res, 207, STATUS_CODES[207], STATUS_CODES[207],
ctype='text/xml; charset="utf-8"')
else:
self.send_status(result_code)
def get_userinfo(self, user, pw):
""" Dummy method which lets all users in """
return 1
def send_status(self, code=200, mediatype='text/xml; charset="utf-8"', \
msg=None, body=None):
if not msg: msg = STATUS_CODES[code]
self.send_body(body, code, STATUS_CODES[code], msg, mediatype)
def send_notFound(self, descr, uri):
body = """<?xml version="1.0" encoding="utf-8" ?>
<D:response xmlns:D="DAV:">
<D:href>%s</D:href>
<D:error/>
<D:responsedescription>%s</D:responsedescription>
</D:response>
"""
return self.send_status(404, descr, body=body % (uri, descr))
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,20 +0,0 @@
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""

View File

@ -1,18 +0,0 @@
"""
constants definition
"""
# definition for resourcetype
COLLECTION=1
OBJECT=None
DAV_PROPS=['creationdate', 'displayname', 'getcontentlanguage', 'getcontentlength', 'getcontenttype', 'getetag', 'getlastmodified', 'lockdiscovery', 'resourcetype', 'source', 'supportedlock']
# Request classes in propfind
RT_ALLPROP=1
RT_PROPNAME=2
RT_PROP=3

View File

@ -1,219 +0,0 @@
"""
davcmd.py
---------
containts commands like copy, move, delete for normal
resources and collections
"""
from string import split,replace,joinfields
import urlparse
from utils import create_treelist, is_prefix
from errors import *
def deltree(dc, uri, exclude={}):
""" delete a tree of resources
dc -- dataclass to use
uri -- root uri to delete
exclude -- an optional list of uri:error_code pairs which should not
be deleted.
returns dict of uri:error_code tuples from which
another method can create a multistatus xml element.
Also note that we only know Depth=infinity thus we don't have
to test for it.
"""
tlist = create_treelist(dc,uri)
result = {}
for i in range(len(tlist),0,-1):
problem_uris = result.keys()
element = tlist[i-1]
# test here, if an element is a prefix of an uri which
# generated an error before.
# note that we walk here from childs to parents, thus
# we cannot delete a parent if a child made a problem.
# (see example in 8.6.2.1)
ok = 1
for p in problem_uris:
if is_prefix(element,p):
ok = None
break
if not ok: continue
# here we test for the exclude list which is the other way round!
for p in exclude.keys():
if is_prefix(p,element):
ok = None
break
if not ok: continue
# now delete stuff
try:
delone(dc,element)
except DAV_Error, (ec,dd):
result[element] = ec
return result
def delone(dc, uri):
""" delete a single object """
if dc.is_collection(uri):
dc.rmcol(uri) # should be empty
else:
dc.rm(uri)
###
### COPY
###
# helper function
def copy(dc, src, dst):
""" only copy the element
This is just a helper method factored out from copy and
copytree. It will not handle the overwrite or depth header.
"""
# destination should have been deleted before
if dc.exists(dst): raise DAV_Error, 412
# source should exist also
if not dc.exists(src): raise DAV_NotFound
if dc.is_collection(src):
dc.copycol(src,dst) # an exception will be passed thru
else:
dc.copy(src,dst) # an exception will be passed thru
# the main functions
def copyone(dc, src, dst, overwrite=None):
""" copy one resource to a new destination """
if overwrite and dc.exists(dst):
delres = deltree(dc,dst)
else:
delres = {}
# if we cannot delete everything, then do not copy!
if delres: return delres
try:
copy(dc,src,dst) # pass thru exceptions
except DAV_Error, (ec,dd):
return ec
def copytree(dc, src, dst, overwrite=None):
""" copy a tree of resources to another location
dc -- dataclass to use
src -- src uri from where to copy
dst -- dst uri
overwrite -- if 1 then delete dst uri before
returns dict of uri:error_code tuples from which
another method can create a multistatus xml element.
"""
# first delete the destination resource
if overwrite and dc.exists(dst):
delres = deltree(dc,dst)
else:
delres = {}
# if we cannot delete everything, then do not copy!
if delres: return delres
# get the tree we have to copy
tlist = create_treelist(dc,src)
result = {}
# prepare destination URIs (get the prefix)
dpath = urlparse.urlparse(dst)[2]
for element in tlist:
problem_uris = result.keys()
# now URIs get longer and longer thus we have
# to test if we had a parent URI which we were not
# able to copy in problem_uris which is the prefix
# of the actual element. If it is, then we cannot
# copy this as well but do not generate another error.
ok = 1
for p in problem_uris:
if is_prefix(p,element):
ok = None
break
if not ok: continue
# now create the destination URI which corresponds to
# the actual source URI. -> actual_dst
# ("subtract" the base src from the URI and prepend the
# dst prefix to it.)
esrc = replace(element,src,"")
actual_dst = dpath+esrc
# now copy stuff
try:
copy(dc,element,actual_dst)
except DAV_Error, (ec,dd):
result[element] = ec
return result
###
### MOVE
###
def moveone(dc, src, dst, overwrite=None):
""" move a single resource
This is done by first copying it and then deleting
the original.
"""
# first copy it
copyone(dc,src,dst,overwrite)
# then delete it
dc.rm(src)
def movetree(dc, src, dst, overwrite=None):
""" move a collection
This is done by first copying it and then deleting
the original.
PROBLEM: if something did not copy then we have a problem
when deleting as the original might get deleted!
"""
# first copy it
res = copytree(dc,src,dst,overwrite)
# then delete it
res = deltree(dc,src,exclude=res)
return res
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,134 +0,0 @@
#!/usr/bin/env python
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from xml.dom import ext
from xml.dom.Document import Document
import sys
import string
import urlparse
import urllib
from StringIO import StringIO
import utils
from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP
from errors import *
from utils import create_treelist, quote_uri, gen_estring
class COPY:
""" copy resources and eventually create multistatus responses
This module implements the COPY class which is responsible for
copying resources. Usually the normal copy work is done in the
interface class. This class only creates error messages if error
occur.
"""
def __init__(self, dataclass, src_uri, dst_uri, overwrite):
self.__dataclass = dataclass
self.__src = src_uri
self.__dst = dst_uri
self.__overwrite = overwrite
def single_action(self):
""" copy a normal resources.
We try to copy it and return the result code.
This is for Depth==0
"""
dc = self.__dataclass
base = self.__src
### some basic tests
# test if dest exists and overwrite is false
if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
# test if src and dst are the same
# (we assume that both uris are on the same server!)
ps = urlparse.urlparse(self.__src)[2]
pd = urlparse.urlparse(self.__dst)[2]
if ps==pd: raise DAV_Error, 403
return dc.copyone(self.__src,self.__dst,self.__overwrite)
#return copyone(dc,self.__src,self.__dst,self.__overwrite)
def tree_action(self):
""" copy a tree of resources (a collection)
Here we return a multistatus xml element.
"""
dc = self.__dataclass
base = self.__src
### some basic tests
# test if dest exists and overwrite is false
if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
# test if src and dst are the same
# (we assume that both uris are on the same server!)
ps = urlparse.urlparse(self.__src)[2]
pd = urlparse.urlparse(self.__dst)[2]
if ps==pd: raise DAV_Error, 403
result = dc.copytree(self.__src,self.__dst,self.__overwrite)
#result=copytree(dc,self.__src,self.__dst,self.__overwrite)
if not result: return None
###
### create the multistatus XML element
### (this is also the same as in delete.py.
### we might make a common method out of it)
###
doc = Document(None)
ms = doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D","DAV:")
doc.appendChild(ms)
for el,ec in result.items():
re = doc.createElement("D:response")
hr = doc.createElement("D:href")
st = doc.createElement("D:status")
huri = doc.createTextNode(quote_uri(el))
t = doc.createTextNode(gen_estring(ec))
st.appendChild(t)
hr.appendChild(huri)
re.appendChild(hr)
re.appendChild(st)
ms.appendChild(re)
sfile = StringIO()
ext.PrettyPrint(doc,stream = sfile)
s = sfile.getvalue()
sfile.close()
return s
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,103 +0,0 @@
#!/usr/bin/env python
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import sys
import string
import urlparse
import urllib
from StringIO import StringIO
import utils
from constants import COLLECTION, OBJECT, DAV_PROPS
from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
from errors import *
from utils import create_treelist, quote_uri, gen_estring, make_xmlresponse
from davcmd import moveone, movetree
class MOVE:
""" move resources and eventually create multistatus responses
This module implements the MOVE class which is responsible for
moving resources.
MOVE is implemented by a COPY followed by a DELETE of the old
resource.
"""
def __init__(self, dataclass, src_uri, dst_uri, overwrite):
self.__dataclass = dataclass
self.__src = src_uri
self.__dst = dst_uri
self.__overwrite = overwrite
def single_action(self):
""" move a normal resources.
We try to move it and return the result code.
This is for Depth==0
"""
dc = self.__dataclass
base = self.__src
### some basic tests
# test if dest exists and overwrite is false
if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
# test if src and dst are the same
# (we assume that both uris are on the same server!)
ps = urlparse.urlparse(self.__src)[2]
pd = urlparse.urlparse(self.__dst)[2]
if ps==pd: raise DAV_Error, 403
return dc.moveone(self.__src,self.__dst,self.__overwrite)
def tree_action(self):
""" move a tree of resources (a collection)
Here we return a multistatus xml element.
"""
dc = self.__dataclass
base = self.__src
### some basic tests
# test if dest exists and overwrite is false
if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
# test if src and dst are the same
# (we assume that both uris are on the same server!)
ps = urlparse.urlparse(self.__src)[2]
pd = urlparse.urlparse(self.__dst)[2]
if ps==pd: raise DAV_Error, 403
result = dc.movetree(self.__src,self.__dst,self.__overwrite)
if not result: return None
# create the multistatus XML element
return make_xmlresponse(result)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,64 +0,0 @@
#!/usr/bin/env python
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import os
import string
import urllib
from StringIO import StringIO
from status import STATUS_CODES
from utils import gen_estring, quote_uri, make_xmlresponse
from davcmd import deltree
class DELETE:
def __init__(self, uri, dataclass):
self.__dataclass = dataclass
self.__uri = uri
def delcol(self):
""" delete a collection """
dc = self.__dataclass
result = dc.deltree(self.__uri)
if not len(result.items()):
return None # everything ok
# create the result element
return make_xmlresponse(result)
def delone(self):
""" delete a resource """
dc = self.__dataclass
result = dc.delone(self.__uri)
if not result: return None
if not len(result.items()):
return None # everything ok
# create the result element
return make_xmlresponse(result)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,57 +0,0 @@
#!/usr/bin/env python
"""
Exceptions for the DAVserver implementation
"""
class DAV_Error(Exception):
""" in general we can have the following arguments:
1. the error code
2. the error result element, e.g. a <multistatus> element
"""
def __init__(self, *args):
if len(args)==1:
self.args = (args[0],"")
else:
self.args = args
class DAV_Secret(DAV_Error):
""" the user is not allowed to know anything about it
returning this for a property value means to exclude it
from the response xml element.
"""
def __init__(self):
DAV_Error.__init__(self,0)
pass
class DAV_NotFound(DAV_Error):
""" a requested property was not found for a resource """
def __init__(self, *args):
if len(args):
if isinstance(args[0], list):
stre = "Path %s not found!"%('/'.join(args[0]))
else:
stre = args[0]
DAV_Error.__init__(self,404,stre)
else:
DAV_Error.__init__(self,404)
pass
class DAV_Forbidden(DAV_Error):
""" a method on a resource is not allowed """
def __init__(self, *args):
if len(args):
DAV_Error.__init__(self,403,args[0])
else:
DAV_Error.__init__(self,403)
pass
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,264 +0,0 @@
"""
basic interface class
use this for subclassing when writing your own interface
class.
"""
from errors import *
import time
from string import lower
class dav_interface:
""" interface class for implementing DAV servers """
### defined properties (modify this but let the DAV stuff there!)
### the format is namespace: [list of properties]
PROPS={"DAV:" : ('creationdate',
'displayname',
'getcontentlanguage',
'getcontentlength',
'getcontenttype',
'getetag',
'getlastmodified',
'lockdiscovery',
'resourcetype',
'source',
'supportedlock'),
"NS2" : ("p1","p2")
}
# here we define which methods handle which namespace
# the first item is the namespace URI and the second one
# the method prefix
# e.g. for DAV:getcontenttype we call dav_getcontenttype()
M_NS = {"DAV:" : "_get_dav",
"NS2" : "ns2" }
def get_propnames(self, uri):
""" return the property names allowed for the given URI
In this method we simply return the above defined properties
assuming that they are valid for any resource.
You can override this in order to return a different set
of property names for each resource.
"""
return self.PROPS
def get_prop2(self, uri, ns, pname):
""" return the value of a property
"""
if lower(ns)=="dav:": return self.get_dav(uri,pname)
raise DAV_NotFound
def get_prop(self, uri, ns, propname):
""" return the value of a given property
uri -- uri of the object to get the property of
ns -- namespace of the property
pname -- name of the property
"""
if self.M_NS.has_key(ns):
prefix = self.M_NS[ns]
else:
print "No namespace:",ns, "( for prop:", propname,")"
raise DAV_NotFound
mname = prefix+"_"+propname
if not hasattr(self,mname):
raise DAV_NotFound
try:
m = getattr(self,mname)
r = m(uri)
return r
except AttributeError, e:
print 'Property %s not supported' % propname
print "Exception:", e
raise DAV_NotFound
###
### DATA methods (for GET and PUT)
###
def get_data(self, uri):
""" return the content of an object
return data or raise an exception
"""
raise DAV_NotFound
def put(self, uri, data):
""" write an object to the repository
return a result code or raise an exception
"""
raise DAV_Forbidden
###
### Methods for DAV properties
###
def _get_dav_creationdate(self, uri):
""" return the creationdate of a resource """
d = self.get_creationdate(uri)
# format it
if isinstance(d, int) or isinstance(d, float):
d = time.localtimetime(d)
return time.strftime("%Y-%m-%dT%H:%M:%S%Z",d)
def _get_dav_getlastmodified(self, uri):
""" return the last modified date of a resource """
d = self.get_lastmodified(uri)
if isinstance(d, int) or isinstance(d, float):
d = time.localtime(d)
# format it
return time.asctime(d)
###
### OVERRIDE THESE!
###
def get_creationdate(self, uri):
""" return the creationdate of the resource """
return time.time()
def get_lastmodified(self, uri):
""" return the last modification date of the resource """
return time.time()
###
### COPY MOVE DELETE
###
### methods for deleting a resource
def rmcol(self, uri):
""" delete a collection
This should not delete any children! This is automatically done
before by the DELETE class in DAV/delete.py
return a success code or raise an exception
"""
raise DAV_NotFound
def rm(self, uri):
""" delete a single resource
return a success code or raise an exception
"""
raise DAV_NotFound
"""
COPY/MOVE HANDLER
These handler are called when a COPY or MOVE method is invoked by
a client. In the default implementation it works as follows:
- the davserver receives a COPY/MOVE method
- the davcopy or davmove module will be loaded and the corresponding
class will be initialized
- this class parses the query and decides which method of the interface class
to call:
copyone for a single resource to copy
copytree for a tree to copy (collection)
(the same goes for move of course).
- the interface class has now two options:
1. to handle the action directly (e.g. cp or mv on filesystems)
2. to let it handle via the copy/move methods in davcmd.
ad 1) The first approach can be used when we know that no error can
happen inside a tree or when the action can exactly tell which
element made which error. We have to collect these and return
it in a dict of the form {uri: error_code, ...}
ad 2) The copytree/movetree/... methods of davcmd.py will do the recursion
themselves and call for each resource the copy/move method of the
interface class. Thus method will then only act on a single resource.
(Thus a copycol on a normal unix filesystem actually only needs to do
an mkdir as the content will be copied by the davcmd.py function.
The davcmd.py method will also automatically collect all errors and
return the dictionary described above.
When you use 2) you also have to implement the copy() and copycol()
methods in your interface class. See the example for details.
To decide which approach is the best you have to decide if your application
is able to generate errors inside a tree. E.g. a function which completely
fails on a tree if one of the tree's childs fail is not what we need. Then
2) would be your way of doing it.
Actually usually 2) is the better solution and should only be replaced by
1) if you really need it.
The remaining question is if we should do the same for the DELETE method.
"""
### MOVE handlers
def moveone(self, src, dst, overwrite):
""" move one resource with Depth=0 """
return moveone(self, src, dst, overwrite)
def movetree(self, src, dst, overwrite):
""" move a collection with Depth=infinity """
return movetree(self, src, dst, overwrite)
### COPY handlers
def copyone(self, src, dst, overwrite):
""" copy one resource with Depth=0 """
return copyone(self, src, dst, overwrite)
def copytree(self, src, dst, overwrite):
""" copy a collection with Depth=infinity """
return copytree(self, src, dst, overwrite)
### low level copy methods (you only need these for method 2)
def copy(self, src, dst):
""" copy a resource with depth==0
You don't need to bother about overwrite or not.
This has been done already.
return a success code or raise an exception if something fails
"""
return 201
def copycol(self, src, dst):
""" copy a resource with depth==infinity
You don't need to bother about overwrite or not.
This has been done already.
return a success code or raise an exception if something fails
"""
return 201
### some utility functions you need to implement
def exists(self, uri):
""" return 1 or None depending on if a resource exists """
return None # no
def is_collection(self, uri):
""" return 1 or None depending on if a resource is a collection """
return None # no
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,373 +0,0 @@
#!/usr/bin/env python
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from xml.dom import ext
from xml.dom.Document import Document
import sys
import string
import urlparse
import urllib
from StringIO import StringIO
import utils
from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP
from errors import *
def utf8str(st):
if isinstance(st,unicode):
return st.encode('utf8')
else:
return str(st)
class PROPFIND:
""" parse a propfind xml element and extract props
It will set the following instance vars:
request_class: ALLPROP | PROPNAME | PROP
proplist: list of properties
nsmap: map of namespaces
The list of properties will contain tuples of the form
(element name, ns_prefix, ns_uri)
"""
def __init__(self, uri, dataclass, depth):
self.request_type = None
self.nsmap = {}
self.proplist = {}
self.default_ns = None
self.__dataclass = dataclass
self.__depth = str(depth)
self.__uri = uri
self.use_full_urls = True
self.__has_body = None # did we parse a body?
def read_propfind(self, xml_doc):
self.request_type,self.proplist,self.namespaces = utils.parse_propfind(xml_doc)
# a violation of the expected logic: client (korganizer) will ask for DAV:resourcetype
# but we also have to return the http://groupdav.org/:resourcetype property!
if self.proplist.has_key('DAV:') and 'resourcetype' in self.proplist['DAV:']:
if not self.proplist.has_key('http://groupdav.org/'):
self.proplist['http://groupdav.org/'] = []
self.proplist['http://groupdav.org/'].append('resourcetype')
if 'DAV:' in self.namespaces: #TMP
self.namespaces.append('http://groupdav.org/')
def createResponse(self):
""" create the multistatus response
This will be delegated to the specific method
depending on which request (allprop, propname, prop)
was found.
If we get a PROPNAME then we simply return the list with empty
values which we get from the interface class
If we get an ALLPROP we first get the list of properties and then
we do the same as with a PROP method.
If the uri doesn't exist, return an xml response with a 404 status
"""
if not self.__dataclass.exists(self.__uri):
raise DAV_NotFound("Path %s doesn't exist" % self.__uri)
if self.request_type==RT_ALLPROP:
return self.create_allprop()
if self.request_type==RT_PROPNAME:
return self.create_propname()
if self.request_type==RT_PROP:
return self.create_prop()
# no body means ALLPROP!
return self.create_allprop()
def create_propname(self):
""" create a multistatus response for the prop names """
dc = self.__dataclass
# create the document generator
doc = Document(None)
ms = doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D","DAV:")
doc.appendChild(ms)
if self.__depth=="0":
pnames = dc.get_propnames(self.__uri)
re = self.mk_propname_response(self.__uri,pnames,doc)
ms.appendChild(re)
elif self.__depth=="1":
pnames = dc.get_propnames(self.__uri)
re = self.mk_propname_response(self.__uri,pnames,doc)
ms.appendChild(re)
for newuri in dc.get_childs(self.__uri):
pnames = dc.get_propnames(newuri)
re = self.mk_propname_response(newuri,pnames,doc)
ms.appendChild(re)
# *** depth=="infinity"
sfile = StringIO()
ext.PrettyPrint(doc,stream = sfile)
s = sfile.getvalue()
sfile.close()
return s
def create_allprop(self):
""" return a list of all properties """
self.proplist = {}
self.namespaces = []
for ns,plist in self.__dataclass.get_propnames(self.__uri).items():
self.proplist[ns] = plist
self.namespaces.append(ns)
return self.create_prop()
def create_prop(self):
""" handle a <prop> request
This will
1. set up the <multistatus>-Framework
2. read the property values for each URI
(which is dependant on the Depth header)
This is done by the get_propvalues() method.
3. For each URI call the append_result() method
to append the actual <result>-Tag to the result
document.
We differ between "good" properties, which have been
assigned a value by the interface class and "bad"
properties, which resulted in an error, either 404
(Not Found) or 403 (Forbidden).
"""
# create the document generator
doc = Document(None)
ms = doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D","DAV:")
doc.appendChild(ms)
if self.__depth=="0":
gp,bp = self.get_propvalues(self.__uri)
res = self.mk_prop_response(self.__uri,gp,bp,doc)
ms.appendChild(res)
elif self.__depth=="1":
gp,bp = self.get_propvalues(self.__uri)
res = self.mk_prop_response(self.__uri,gp,bp,doc)
ms.appendChild(res)
try:
for newuri in self.__dataclass.get_childs(self.__uri):
gp,bp = self.get_propvalues(newuri)
res = self.mk_prop_response(newuri,gp,bp,doc)
ms.appendChild(res)
except DAV_NotFound:
# If no children, never mind.
pass
sfile = StringIO()
ext.PrettyPrint(doc,stream = sfile)
s = sfile.getvalue()
sfile.close()
return s
def mk_propname_response(self,uri,propnames,doc):
""" make a new <prop> result element for a PROPNAME request
This will simply format the propnames list.
propnames should have the format {NS1 : [prop1, prop2, ...], NS2: ...}
"""
re = doc.createElement("D:response")
# write href information
href = doc.createElement("D:href")
if self.use_full_urls:
huri = doc.createTextNode(uri)
else:
uparts = urlparse.urlparse(uri)
fileloc = uparts[2]
huri = doc.createTextNode(urllib.quote(fileloc.encode('utf8')))
href.appendChild(huri)
re.appendChild(href)
ps = doc.createElement("D:propstat")
nsnum = 0
for ns,plist in propnames.items():
# write prop element
pr = doc.createElement("D:prop")
nsp = "ns"+str(nsnum)
pr.setAttribute("xmlns:"+nsp,ns)
nsnum = nsnum+1
# write propertynames
for p in plist:
pe = doc.createElement(nsp+":"+p)
pr.appendChild(pe)
ps.appendChild(pr)
re.appendChild(ps)
return re
def mk_prop_response(self,uri,good_props,bad_props,doc):
""" make a new <prop> result element
We differ between the good props and the bad ones for
each generating an extra <propstat>-Node (for each error
one, that means).
"""
re = doc.createElement("D:response")
# append namespaces to response
nsnum = 0
for nsname in self.namespaces:
re.setAttribute("xmlns:ns"+str(nsnum),nsname)
nsnum = nsnum+1
# write href information
href = doc.createElement("D:href")
if self.use_full_urls:
huri = doc.createTextNode(uri)
else:
uparts = urlparse.urlparse(uri)
fileloc = uparts[2]
huri = doc.createTextNode(urllib.quote(fileloc.encode('utf8')))
href.appendChild(huri)
re.appendChild(href)
# write good properties
if good_props and len(good_props.items()):
ps = doc.createElement("D:propstat")
gp = doc.createElement("D:prop")
for ns in good_props.keys():
ns_prefix="ns"+str(self.namespaces.index(ns))+":"
for p,v in good_props[ns].items():
pe = doc.createElement(ns_prefix+str(p))
if v == None:
pass
elif ns=='DAV:' and p=="resourcetype":
if v == 1:
ve=doc.createElement("D:collection")
pe.appendChild(ve)
elif isinstance(v,tuple) and v[1] == ns:
ve=doc.createElement(ns_prefix+v[0])
pe.appendChild(ve)
else:
ve = doc.createTextNode(utf8str(v))
pe.appendChild(ve)
gp.appendChild(pe)
if gp.hasChildNodes():
re.appendChild(ps)
ps.appendChild(gp)
s = doc.createElement("D:status")
t = doc.createTextNode("HTTP/1.1 200 OK")
s.appendChild(t)
ps.appendChild(s)
re.appendChild(ps)
# now write the errors!
if len(bad_props.items()):
# write a propstat for each error code
for ecode in bad_props.keys():
ps = doc.createElement("D:propstat")
re.appendChild(ps)
bp = doc.createElement("D:prop")
ps.appendChild(bp)
for ns in bad_props[ecode].keys():
ns_prefix = "ns"+str(self.namespaces.index(ns))+":"
for p in bad_props[ecode][ns]:
pe = doc.createElement(ns_prefix+str(p))
bp.appendChild(pe)
s = doc.createElement("D:status")
t = doc.createTextNode(utils.gen_estring(ecode))
s.appendChild(t)
ps.appendChild(s)
re.appendChild(ps)
# return the new response element
return re
def get_propvalues(self,uri):
""" create lists of property values for an URI
We create two lists for an URI: the properties for
which we found a value and the ones for which we
only got an error, either because they haven't been
found or the user is not allowed to read them.
"""
good_props = {}
bad_props = {}
for (ns,plist) in self.proplist.items():
good_props[ns] = {}
bad_props = {}
ec = 0
for prop in plist:
try:
ec = 0
r = self.__dataclass.get_prop(uri,ns,prop)
good_props[ns][prop] = r
except DAV_Error, error_code:
ec = error_code[0]
# ignore props with error_code if 0 (invisible)
if ec==0: continue
if bad_props.has_key(ec):
if bad_props[ec].has_key(ns):
bad_props[ec][ns].append(prop)
else:
bad_props[ec][ns] = [prop]
else:
bad_props[ec] = {ns:[prop]}
return good_props, bad_props
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,31 +0,0 @@
"""
status codes for DAV services
"""
STATUS_CODES={
102: "Processing",
200: "Ok",
201: "Created",
204: "No Content",
207: "Multi-Status",
201: "Created",
400: "Bad Request",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
409: "Conflict",
412: "Precondition failed",
423: "Locked",
415: "Unsupported Media Type",
507: "Insufficient Storage",
422: "Unprocessable Entity",
423: "Locked",
424: "Failed Dependency",
502: "Bad Gateway",
507: "Insufficient Storage",
999: "Some error in Create Method please check the data of create method"
}

View File

@ -1,161 +0,0 @@
#!/usr/bin/env python
"""
UTILITIES
- parse a propfind request body into a list of props
"""
from xml.dom import ext
from xml.dom.Document import Document
from xml.dom.ext.reader import PyExpat
from xml.dom import Node
from xml.dom import NodeIterator, NodeFilter
from string import lower, split, atoi, joinfields
import urlparse
from StringIO import StringIO
from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
from status import STATUS_CODES
VERSION = '0.6'
AUTHOR = 'Simon Pamies <s.pamies@banality.de>'
def gen_estring(ecode):
""" generate an error string from the given code """
ec = atoi(str(ecode))
if STATUS_CODES.has_key(ec):
return "HTTP/1.1 %s %s" %(ec,STATUS_CODES[ec])
else:
return "HTTP/1.1 %s" %(ec)
def parse_propfind(xml_doc):
""" parse an propfind xml file and return a list of props
returns:
request_type -- ALLPROP, PROPNAME, PROP
proplist -- list of properties found
namespaces -- list of namespaces found
"""
doc = PyExpat.Reader().fromString(xml_doc)
snit = doc.createNodeIterator(doc, NodeFilter.NodeFilter.SHOW_ELEMENT, None, None)
request_type = None
props = {}
namespaces = []
while 1:
curr_elem = snit.nextNode()
if not curr_elem: break
ename=fname = lower(curr_elem.nodeName)
if ":" in fname:
ename = split(fname,":")[1]
if ename=="prop": request_type = RT_PROP; continue
if ename=="propfind": continue
if ename=="allprop": request_type = RT_ALLPROP; continue
if ename=="propname": request_type = RT_PROPNAME; continue
# rest should be names of attributes
ns = curr_elem.namespaceURI
if props.has_key(ns):
props[ns].append(ename)
else:
props[ns] = [ename]
namespaces.append(ns)
return request_type,props,namespaces
def create_treelist(dataclass, uri):
""" create a list of resources out of a tree
This function is used for the COPY, MOVE and DELETE methods
uri - the root of the subtree to flatten
It will return the flattened tree as list
"""
queue = [uri]
list = [uri]
while len(queue):
element = queue[-1]
if dataclass.is_collection(element):
childs = dataclass.get_childs(element)
else:
childs = []
if len(childs):
list = list+childs
# update queue
del queue[-1]
if len(childs):
queue = queue+childs
return list
def is_prefix(uri1, uri2):
""" returns 1 of uri1 is a prefix of uri2 """
if uri2[:len(uri1)]==uri1:
return 1
else:
return None
def quote_uri(uri):
""" quote an URL but not the protocol part """
import urlparse
import urllib
up = urlparse.urlparse(uri)
np = urllib.quote(up[2])
return urlparse.urlunparse((up[0], up[1], np, up[3], up[4], up[5]))
def get_uriparentpath(uri):
""" extract the uri path and remove the last element """
up = urlparse.urlparse(uri)
return joinfields(split(up[2], "/")[:-1], "/")
def get_urifilename(uri):
""" extract the uri path and return the last element """
up = urlparse.urlparse(uri)
return split(up[2], "/")[-1]
def get_parenturi(uri):
""" return the parent of the given resource"""
up = urlparse.urlparse(uri)
np = joinfields(split(up[2], "/")[:-1], "/")
return urlparse.urlunparse((up[0], up[1], np, up[3], up[4], up[5]))
### XML utilities
def make_xmlresponse(result):
""" construct a response from a dict of uri:error_code elements """
doc = Document(None)
ms = doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D", "DAV:")
doc.appendChild(ms)
for el, ec in result.items():
re = doc.createElement("D:response")
hr = doc.createElement("D:href")
st = doc.createElement("D:status")
huri = doc.createTextNode(quote_uri(el))
t = doc.createTextNode(gen_estring(ec))
st.appendChild(t)
hr.appendChild(huri)
re.appendChild(hr)
re.appendChild(st)
ms.appendChild(re)
sfile = StringIO()
ext.PrettyPrint(doc, stream = sfile)
s = sfile.getvalue()
sfile.close()
return s
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -19,11 +19,9 @@
#
##############################################################################
import calendar
import calendar_collection
import caldav
import caldav_cache
import caldav_fs
import caldav_node
import webdav_server
import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -25,6 +25,7 @@
"version" : "1.0",
"depends" : [
"base",
"document_webdav",
],
'description': """
This module Contains basic functionality for caldav system like:
@ -34,7 +35,7 @@
- Provides iCal Import/Export functionality
To access OpenERP Calendar using caldav to remote site use the URL like:
http://HOSTNAME:PORT/calendar/DATABASE_NAME/CALENDAR_NAME.ics
http://HOSTNAME:PORT/webdav/DATABASE_NAME/Calendars/CALENDAR_NAME.ics
Where,
HOSTNAME: Host on which OpenERP server(With webdav) is running

File diff suppressed because it is too large Load Diff

View File

@ -1,62 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
import time
import heapq
def memoize(maxsize):
"""decorator to 'memoize' a function - caching its results"""
def decorating_function(f):
cache = {} # map from key to value
heap = [] # list of keys, in LRU heap
cursize = 0 # because len() is slow
def wrapper(*args):
key = repr(args)
# performance crap
_cache = cache
_heap = heap
_heappop = heapq.heappop
_heappush = heapq.heappush
_time = time.time
_cursize = cursize
_maxsize = maxsize
if not _cache.has_key(key):
if _cursize == _maxsize:
# pop oldest element
(_, oldkey) = _heappop(_heap)
_cache.pop(oldkey)
else:
_cursize += 1
# insert this element
_cache[key] = f(*args)
_heappush(_heap, (_time(), key))
wrapper.misses += 1
else:
wrapper.hits += 1
return cache[key]
wrapper.__doc__ = f.__doc__
wrapper.__name__ = f.__name__
wrapper.hits = wrapper.misses = 0
return wrapper
return decorating_function
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,380 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
<record model="document.directory" id="document.dir_calendars">
<field name="name">Calendars</field>
<field name="calendar_collection">True</field>
</record>
<!-- Event attributes-->
<record model="basic.calendar.attributes" id="field_event_comment">
<field name="name">comment</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_uid">
<field name="name">uid</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_seq">
<field name="name">seq</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_recurrence-id">
<field name="name">recurrence-id</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_transp">
<field name="name">transp</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_attendee">
<field name="name">attendee</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_related">
<field name="name">related</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_rrule">
<field name="name">rrule</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_dtend">
<field name="name">dtend</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_valarm">
<field name="name">valarm</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_vtimezone">
<field name="name">vtimezone</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_priority">
<field name="name">priority</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_location">
<field name="name">location</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_exrule">
<field name="name">exrule</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_resources">
<field name="name">resources</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_rstatus">
<field name="name">rstatus</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_status">
<field name="name">status</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_exdate">
<field name="name">exdate</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_dtstamp">
<field name="name">dtstamp</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_description">
<field name="name">description</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_rdate">
<field name="name">rdate</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_dtstart">
<field name="name">dtstart</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_class">
<field name="name">class</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_x-openobject-model">
<field name="name">x-openobject-model</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_created">
<field name="name">created</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_url">
<field name="name">url</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_summary">
<field name="name">summary</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_contact">
<field name="name">contact</field>
<field name="type">vevent</field>
</record>
<!-- Todo attributes-->
<record model="basic.calendar.attributes" id="field_todo_status">
<field name="name">status</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_comment">
<field name="name">comment</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_attendee">
<field name="name">attendee</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_valarm">
<field name="name">valarm</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_description">
<field name="name">description</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_seq">
<field name="name">seq</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_vtimezone">
<field name="name">vtimezone</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_url">
<field name="name">url</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_completed">
<field name="name">completed</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_percent">
<field name="name">percent</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_due">
<field name="name">due</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_summary">
<field name="name">summary</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_priority">
<field name="name">priority</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_exdate">
<field name="name">exdate</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_location">
<field name="name">location</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_exrule">
<field name="name">exrule</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_duration">
<field name="name">duration</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_organizer">
<field name="name">organizer</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_dtstart">
<field name="name">dtstart</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_rrule">
<field name="name">rrule</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_class">
<field name="name">class</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_uid">
<field name="name">uid</field>
<field name="type">vtodo</field>
</record>
<!-- Attendee's attributes-->
<record model="basic.calendar.attributes" id="field_attendee_cn">
<field name="name">cn</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_sent-by">
<field name="name">sent-by</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_language">
<field name="name">language</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_delegated-from">
<field name="name">delegated-from</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_member">
<field name="name">member</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_cutype">
<field name="name">cutype</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_role">
<field name="name">role</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_partstat">
<field name="name">partstat</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_delegated-to">
<field name="name">delegated-to</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_dir">
<field name="name">dir</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_rsvp">
<field name="name">rsvp</field>
<field name="type">attendee</field>
</record>
<!-- Alarm attributes-->
<record model="basic.calendar.attributes" id="field_alarm_attendee">
<field name="name">attendee</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_trigger_duration">
<field name="name">trigger_duration</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_description">
<field name="name">description</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_attach">
<field name="name">attach</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_trigger_occurs">
<field name="name">trigger_occurs</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_trigger_interval">
<field name="name">trigger_interval</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_summary">
<field name="name">summary</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_duration">
<field name="name">duration</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_repeat">
<field name="name">repeat</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_action">
<field name="name">action</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_trigger_related">
<field name="name">trigger_related</field>
<field name="type">alarm</field>
</record>
</data>
</openerp>

View File

@ -1,334 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
import pooler
import base64
import sys
import os
import time
from string import joinfields, split, lower
from service import security
import netsvc
import urlparse
from DAV.constants import COLLECTION, OBJECT
from DAV.errors import *
from DAV.iface import *
import urllib
from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
from caldav_cache import memoize
from tools import misc
CACHE_SIZE=20000
class tinydav_handler(dav_interface):
"""
This class models a Tiny ERP interface for the DAV server
"""
PROPS={'DAV:': dav_interface.PROPS['DAV:'], }
M_NS={ "DAV:": dav_interface.M_NS['DAV:'], }
def __init__(self, parent, verbose=False):
self.db_name = False
self.parent = parent
self.baseuri = parent.baseuri
def get_propnames(self, uri):
props = self.PROPS
self.parent.log_message('get propnames: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return props
node = self.uri2object(cr, uid, pool, uri2)
if node:
props.update(node.get_dav_props(cr))
cr.close()
return props
def get_prop(self,uri,ns,propname):
""" return the value of a given property
uri -- uri of the object to get the property of
ns -- namespace of the property
pname -- name of the property
"""
if self.M_NS.has_key(ns):
return dav_interface.get_prop(self, uri, ns, propname)
if uri[-1]=='/':uri = uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
raise DAV_NotFound
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound
res = node.get_dav_eprop(cr, ns, propname)
cr.close()
return res
def urijoin(self,*ajoin):
""" Return the base URI of this request, or even join it with the
ajoin path elements
"""
return self.baseuri+ '/'.join(ajoin)
def uri2local(self, uri):
uparts = urlparse.urlparse(uri)
reluri = uparts[2]
if reluri and reluri[-1]=="/":
reluri = reluri[:-1]
return reluri
#
# pos: -1 to get the parent of the uri
#
def get_cr(self, uri):
pdb = self.parent.auth_proxy.last_auth
reluri = self.uri2local(uri)
try:
dbname = reluri.split('/')[2]
except:
dbname = False
if not dbname:
return None, None, None, False, None
if not pdb and dbname:
# if dbname was in our uri, we should have authenticated
# against that.
raise Exception("Programming error")
assert pdb == dbname, " %s != %s" %(pdb, dbname)
user, passwd, dbn2, uid = self.parent.auth_proxy.auth_creds[pdb]
db,pool = pooler.get_db_and_pool(dbname)
cr = db.cursor()
uri2 = reluri.split('/')[3:]
return cr, uid, pool, dbname, uri2
def uri2object(self, cr, uid, pool, uri):
if not uid:
return None
return pool.get('basic.calendar').get_calendar_object(cr, uid, uri)
def get_data(self, uri):
self.parent.log_message('GET: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
raise DAV_Error, 409
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
try:
datas = node.get_data(cr, uid)
except TypeError, e:
import traceback
self.parent.log_error("GET typeError: %s", str(e))
self.parent.log_message("Exc: %s", traceback.format_exc())
raise DAV_Forbidden
except IndexError, e :
self.parent.log_error("GET IndexError: %s", str(e))
raise DAV_NotFound(uri2)
except Exception, e:
import traceback
self.parent.log_error("GET exception: %s", str(e))
self.parent.log_message("Exc: %s", traceback.format_exc())
raise DAV_Error, 409
return datas
finally:
cr.close()
@memoize(CACHE_SIZE)
def _get_dav_resourcetype(self, uri):
""" return type of object """
self.parent.log_message('get RT: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
return COLLECTION
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
return OBJECT
finally:
cr.close()
def _get_dav_displayname(self, uri):
self.parent.log_message('get DN: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return COLLECTION
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound(uri2)
cr.close()
return node.displayname
@memoize(CACHE_SIZE)
def _get_dav_getcontentlength(self, uri):
""" return the content length of an object """
self.parent.log_message('get length: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
result = 0
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return '0'
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound(uri2)
result = node.content_length or 0
cr.close()
return str(result)
@memoize(CACHE_SIZE)
def _get_dav_getetag(self, uri):
""" return the ETag of an object """
self.parent.log_message('get etag: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
result = 0
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return '0'
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound(uri2)
result = node.get_etag(cr)
cr.close()
return str(result)
@memoize(CACHE_SIZE)
def get_lastmodified(self, uri):
""" return the last modified date of the object """
if uri[-1]=='/':uri=uri[:-1]
today = time.time()
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
return today
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
if node.write_date:
return time.mktime(time.strptime(node.write_date, '%Y-%m-%d %H:%M:%S'))
else:
return today
finally:
cr.close()
@memoize(CACHE_SIZE)
def get_creationdate(self, uri):
""" return the last modified date of the object """
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
raise DAV_Error, 409
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
if node.create_date:
result = time.strptime(node.create_date, '%Y-%m-%d %H:%M:%S')
else:
result = time.gmtime()
return result
finally:
cr.close()
@memoize(CACHE_SIZE)
def _get_dav_getcontenttype(self, uri):
self.parent.log_message('get contenttype: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
return 'httpd/unix-directory'
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
result = node.mimetype
return result
#raise DAV_NotFound, 'Could not find %s' % path
finally:
cr.close()
def put(self, uri, data, content_type=None):
""" put the object into the filesystem """
self.parent.log_message('Putting %s (%d), %s'%(misc.ustr(uri), len(data), content_type))
parent='/'.join(uri.split('/')[:-1])
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
raise DAV_Forbidden
try:
node = self.uri2object(cr, uid, pool, uri2[:])
except:
node = False
if not node:
raise DAV_Forbidden
else:
try:
node.set_data(cr, uid, data)
except Exception, e:
import traceback
self.parent.log_error("Cannot save :%s", str(e))
self.parent.log_message("Exc: %s", traceback.format_exc())
raise DAV_Forbidden
cr.commit()
cr.close()
return 201
def exists(self, uri):
""" test if a resource exists """
result = False
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return True
try:
node = self.uri2object(cr, uid, pool, uri2)
if node:
result = True
except:
pass
cr.close()
return result
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -25,31 +25,104 @@ import pooler
import tools
import time
import base64
from document import nodes
import StringIO
class node_database(nodes.node_database):
def _child_get(self, cr, name=False, parent_id=False, domain=None):
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
if not domain:
domain = []
domain2 = domain + [('calendar_collection','=', False)]
res = super(node_database, self)._child_get(cr, name=name, parent_id=parent_id, domain=domain2)
where = [('parent_id','=',parent_id)]
domain2 = domain + [('calendar_collection','=', True)]
if name:
where.append(('name','=',name))
if domain2:
where += domain2
where2 = where + [('type', '=', 'directory')]
ids = dirobj.search(cr, uid, where2, context=ctx)
for dirr in dirobj.browse(cr,uid,ids,context=ctx):
res.append(node_calendar_collection(dirr.name,self,self.context,dirr))
return res
class node_calendar_collection(nodes.node_dir):
PROPS = {
"http://calendarserver.org/ns/" : ('getctag'),
}
M_NS = {
"http://calendarserver.org/ns/" : '_get_dav',
}
class node_calendar(object):
def __init__(self, path, context, calendar):
self.path = path
self.context = context
self.calendar_id = calendar.id
self.mimetype = 'ics'
self.create_date = calendar.create_date
self.write_date = calendar.write_date or calendar.create_date
self.content_length = 0
self.displayname = calendar.name
def get_dav_props(self, cr):
return self.PROPS
def get_data(self, cr, uid):
calendar_obj = pooler.get_pool(cr.dbname).get('basic.calendar')
return calendar_obj.export_cal(cr, uid, [self.calendar_id])
def get_dav_eprop(self,cr, ns, propname):
if self.M_NS.has_key(ns):
prefix = self.M_NS[ns]
else:
print "No namespace:",ns, "( for prop:", propname,")"
return None
def get_data_len(self, cr):
return self.content_length
mname = prefix + "_" + propname
def set_data(self, cr, uid, data):
calendar_obj = pooler.get_pool(cr.dbname).get('basic.calendar')
return calendar_obj.import_cal(cr, uid, base64.encodestring(data), self.calendar_id)
if not hasattr(self, mname):
return None
try:
m = getattr(self, mname)
r = m(cr)
return r
except AttributeError, e:
print 'Property %s not supported' % propname
print "Exception:", e
return None
def _file_get(self,cr, nodename=False):
return []
def _child_get(self, cr, name=False, parent_id=False, domain=None):
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('collection_id','=',self.dir_id)]
ext = False
if name:
res = name.split('.ics')
if len(res) > 1:
name = res[0]
ext = '.ics'
if name:
where.append(('name','=',name))
if not domain:
domain = []
where = where + domain
fil_obj = dirobj.pool.get('basic.calendar')
ids = fil_obj.search(cr,uid,where,context=ctx)
res = []
for calender in fil_obj.browse(cr, uid, ids, context=ctx):
if not ext:
res.append(node_calendar(calender.name, self, self.context, calender))
else:
res.append(res_node_calendar(name, self, self.context, calender))
return res
def _get_dav_owner(self, cr):
return False
def get_etag(self, cr):
""" Get a tag, unique per object + modification.
@ -64,24 +137,385 @@ class node_calendar(object):
return str(wtime)
def _get_ttag(self, cr):
return 'calendar-%d' % self.calendar_id
return 'calendar collection-%d' % self.dir_id
def _get_dav_getctag(self, cr):
result = self.get_etag(cr)
return str(result)
class node_calendar(nodes.node_class):
our_type = 'collection'
PROPS = {
"http://calendarserver.org/ns/" : ('getctag'),
"urn:ietf:params:xml:ns:caldav" : (
'calendar-description',
'calendar-data',
'calendar-home-set',
'calendar-user-address-set',
'schedule-inbox-URL',
'schedule-outbox-URL',)}
M_NS = {
"DAV:" : '_get_dav',
"http://calendarserver.org/ns/" : '_get_dav',
"urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
def __init__(self,path, parent, context, calendar):
super(node_calendar,self).__init__(path, parent,context)
self.calendar_id = calendar.id
self.mimetype = 'application/x-directory'
self.create_date = calendar.create_date
self.write_date = calendar.write_date or calendar.create_date
self.content_length = 0
self.displayname = calendar.name
self.cal_type = calendar.type
def _get_dav_getctag(self, cr):
result = self._get_ttag(cr) + ':' + str(time.time())
return str(result)
def match_dav_eprop(self, cr, match, ns, prop):
if ns == "DAV:" and prop == "getetag":
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
tem, dav_time = tuple(match.split(':'))
model, res_id = tuple(tem.split('_'))
model_obj = dirobj.pool.get(model)
model = model_obj.browse(cr, uid, res_id, context=ctx)
write_time = model.write_date or model.create_date
wtime = time.mktime(time.strptime(write_time,'%Y-%m-%d %H:%M:%S'))
if float(dav_time) == float(wtime):
return True
return False
res = super(node_calendar, self).match_dav_eprop(cr, match, ns, prop)
return res
def get_domain(self, cr, filters):
res = []
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
calendar_obj = dirobj.pool.get('basic.calendar')
if not filters:
return res
if filters.localName == 'calendar-query':
res = []
for filter_child in filters.childNodes:
if filter_child.nodeType == filter_child.TEXT_NODE:
continue
if filter_child.localName == 'filter':
for vcalendar_filter in filter_child.childNodes:
if vcalendar_filter.nodeType == vcalendar_filter.TEXT_NODE:
continue
if vcalendar_filter.localName == 'comp-filter':
if vcalendar_filter.getAttribute('name') == 'VCALENDAR':
for vevent_filter in vcalendar_filter.childNodes:
if vevent_filter.nodeType == vevent_filter.TEXT_NODE:
continue
if vevent_filter.localName == 'comp-filter':
if vevent_filter.getAttribute('name') == 'VEVENT':
res = [('type','=','vevent')]
if vevent_filter.getAttribute('name') == 'VTODO':
res = [('type','=','vtodo')]
return res
elif filters.localName == 'calendar-multiget':
names = []
for filter_child in filters.childNodes:
if filter_child.nodeType == filter_child.TEXT_NODE:
continue
if filter_child.localName == 'href':
if not filter_child.firstChild:
continue
uri = filter_child.firstChild.data
caluri = uri.split('/')
if len(caluri):
caluri = caluri[-2]
if caluri not in names : names.append(caluri)
res = [('name','in',names)]
return res
return res
def children(self, cr, domain=None):
return self._child_get(cr, domain=domain)
def child(self,cr, name, domain=None):
res = self._child_get(cr, name, domain=domain)
if res:
return res[0]
return None
class Calendar(osv.osv):
_inherit = 'basic.calendar'
def _child_get(self, cr, name=False, parent_id=False, domain=None):
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = []
if name:
where.append(('id','=',int(name)))
if not domain:
domain = []
#for opr1, opt, opr2 in domain:
# if opr1 == 'type' and opr2 != self.cal_type:
# return []
def get_calendar_object(self, cr, uid, uri, context=None):
if not uri:
fil_obj = dirobj.pool.get('basic.calendar')
ids = fil_obj.search(cr, uid, domain)
res = []
if self.calendar_id in ids:
res = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self, domain=where, context=ctx)
return res
def get_dav_props(self, cr):
return self.PROPS
def get_dav_eprop(self,cr, ns, propname):
if self.M_NS.has_key(ns):
prefix = self.M_NS[ns]
else:
print "No namespace:",ns, "( for prop:", propname,")"
return None
if len(uri) > 1:
propname = propname.replace('-','_')
mname = prefix + "_" + propname
if not hasattr(self, mname):
return None
name, file_type = tuple(uri[0].split('.'))
res = self.name_search(cr, uid, name)
if not res:
return None
calendar_id, calendar_name = res[0]
calendar = self.browse(cr, uid, calendar_id)
return node_calendar(uri, context, calendar)
Calendar()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4
try:
m = getattr(self, mname)
r = m(cr)
return r
except AttributeError, e:
print 'Property %s not supported' % propname
print "Exception:", e
return None
def create_child(self,cr,path,data):
""" API function to create a child file object and node
Return the node_* created
"""
return self.set_data(cr, data)
def set_data(self, cr, data, fil_obj = None):
uid = self.context.uid
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
return calendar_obj.import_cal(cr, uid, base64.encodestring(data), self.calendar_id)
def get_data_len(self, cr, fil_obj = None):
return self.content_length
def _get_ttag(self,cr):
return 'calendar-%d' % (self.calendar_id,)
def get_etag(self, cr):
""" Get a tag, unique per object + modification.
see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
return self._get_ttag(cr) + ':' + self._get_wtag(cr)
def _get_wtag(self, cr):
""" Return the modification time as a unique, compact string """
if self.write_date:
wtime = time.mktime(time.strptime(self.write_date, '%Y-%m-%d %H:%M:%S'))
else: wtime = time.time()
return str(wtime)
def rmcol(self, cr):
return False
class res_node_calendar(nodes.node_class):
our_type = 'file'
PROPS = {
"http://calendarserver.org/ns/" : ('getctag'),
"urn:ietf:params:xml:ns:caldav" : (
'calendar-description',
'calendar-data',
'calendar-home-set',
'calendar-user-address-set',
'schedule-inbox-URL',
'schedule-outbox-URL',)}
M_NS = {
"http://calendarserver.org/ns/" : '_get_dav',
"urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
def __init__(self,path, parent, context, res_obj, res_model=None, res_id=None):
super(res_node_calendar,self).__init__(path, parent, context)
self.mimetype = 'text/calendar'
self.create_date = parent.create_date
self.write_date = parent.write_date or parent.create_date
self.calendar_id = hasattr(parent, 'calendar_id') and parent.calendar_id or False
if res_obj:
if not self.calendar_id: self.calendar_id = res_obj.id
self.create_date = res_obj.create_date
self.write_date = res_obj.write_date or res_obj.create_date
self.displayname = res_obj.name
self.content_length = 0
self.model = res_model
self.res_id = res_id
def open(self, cr, mode=False):
uid = self.context.uid
if self.type in ('collection','database'):
return False
s = StringIO.StringIO(self.get_data(cr))
s.name = self
return s
def get_dav_props(self, cr):
return self.PROPS
def get_dav_eprop(self,cr, ns, propname):
if self.M_NS.has_key(ns):
prefix = self.M_NS[ns]
else:
print "No namespace:",ns, "( for prop:", propname,")"
return None
propname = propname.replace('-','_')
mname = prefix + "_" + propname
if not hasattr(self, mname):
return None
try:
m = getattr(self, mname)
r = m(cr)
return r
except AttributeError, e:
print 'Property %s not supported' % propname
print "Exception:", e
return None
def get_data(self, cr, fil_obj = None):
uid = self.context.uid
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
context = self.context.context.copy()
context.update({'model': self.model, 'res_id':self.res_id})
res = calendar_obj.export_cal(cr, uid, [self.calendar_id], context=context)
return res
def get_data_len(self, cr, fil_obj = None):
return self.content_length
def set_data(self, cr, data, fil_obj = None):
uid = self.context.uid
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
return calendar_obj.import_cal(cr, uid, base64.encodestring(data), self.calendar_id)
def _get_ttag(self,cr):
res = False
if self.model and self.res_id:
res = '%s_%d' % (self.model, self.res_id)
elif self.calendar_id:
res = '%d' % (self.calendar_id)
return res
def _get_caldav_calendar_data(self, cr):
return self.get_data(cr)
def _get_caldav_calendar_description(self, cr):
uid = self.context.uid
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
ctx = self.context.context.copy()
ctx.update(self.dctx)
calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
return calendar.description
def _get_caldav_calendar_home_set(self, cr):
import xml.dom.minidom
import urllib
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, calendar.collection_id.name)))
href = doc.documentElement
href.tagName = 'D:href'
href.appendChild(huri)
return href
def _get_caldav_calendar_user_address_set(self, cr):
import xml.dom.minidom
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
user_obj = self.context._dirobj.pool.get('res.users')
user = user_obj.browse(cr, uid, uid, context=ctx)
doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
href = doc.documentElement
href.tagName = 'D:href'
huri = doc.createTextNode('MAILTO:' + user.email)
href.appendChild(huri)
return href
def _get_caldav_schedule_inbox_URL(self, cr):
import xml.dom.minidom
import urllib
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
res = '%s/%s' %(calendar.name, calendar.collection_id.name)
doc = xml.dom.minidom.getDOMImplementation().createDocument(None, 'href', None)
href = doc.documentElement
href.tagName = 'D:href'
huri = doc.createTextNode(urllib.quote('/%s/%s' % (cr.dbname, res)))
href.appendChild(huri)
return href
def rm(self, cr):
uid = self.context.uid
res = False
if self.type in ('collection','database'):
return False
if self.model and self.res_id:
document_obj = self.context._dirobj.pool.get(self.model)
if document_obj:
res = False
#res = document_obj.unlink(cr, uid, [self.res_id]) #TOFIX
return res
def _get_caldav_schedule_outbox_URL(self, cr):
return self._get_caldav_schedule_inbox_URL(cr)
def get_etag(self, cr):
""" Get a tag, unique per object + modification.
see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
return self._get_ttag(cr) + ':' + self._get_wtag(cr)
def _get_wtag(self, cr):
""" Return the modification time as a unique, compact string """
if self.write_date:
wtime = time.mktime(time.strptime(self.write_date, '%Y-%m-%d %H:%M:%S'))
else: wtime = time.time()
return str(wtime)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4

View File

@ -1,61 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<data>
<record model="ir.ui.view" id="view_calendar_collection_form">
<field name="name">Calendar Collections : Form</field>
<field name="model">document.directory</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Calendar Collections">
<field name="name" select="1" colspan="4"/>
<field name="user_id"/>
<field name="parent_id"/>
<field name="calendar_ids" colspan="4" nolabel="1"/>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_calendar_collection_tree">
<field name="name">Calendar Collections : Tree</field>
<field name="model">document.directory</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Calendar Collections" toolbar="1">
<field name="name"/>
<field name="user_id"/>
<field name="create_date"/>
<field name="write_date"/>
</tree>
</field>
</record>
<record id="view_calendar_form" model="ir.ui.view">
<field name="name">Basic Calendar</field>
<record model="ir.actions.act_window" id="action_calendar_collection_form">
<field name="name">Calendar Collections</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">document.directory</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('calendar_collection','=',True)]</field>
</record>
<record model="ir.actions.act_window" id="document.action_document_directory_form">
<field name="domain">[('calendar_collection','=',False)]</field>
</record>
<record id="action_dir_view1" model="ir.actions.act_window.view">
<field eval="10" name="sequence"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_calendar_collection_tree"/>
<field name="act_window_id" ref="action_calendar_collection_form"/>
</record>
<record id="action_dir_view2" model="ir.actions.act_window.view">
<field eval="20" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_calendar_collection_form"/>
<field name="act_window_id" ref="action_calendar_collection_form"/>
</record>
<menuitem
id="menu_calendar"
name="Calendar"
parent="base.menu_document_configuration"/>
<menuitem
action="action_calendar_collection_form"
id="menu_calendar_collection"
parent="menu_calendar"/>
<record model="ir.ui.view" id="view_caldav_form">
<field name="name">Calendar : Form</field>
<field name="model">basic.calendar</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Basic Calendar">
<field name="name" required="1" select="1" />
<field name="active" select="1" />
<form string="Calendar">
<field name="name"/>
<field name="type"/>
<field name="user_id"/>
<field name="collection_id" required="1"/>
<field name="line_ids" mode="form,tree" colspan="4" nolabel="1">
<form string="Calendar Lines">
<field name="name" required="1" select="1" />
<field name="object_id" required="1" select="1" />
<field name="domain" select="1" />
<field name="mapping_ids" select="1" colspan="4" nolabel="1">
<tree string="Attributes Mapping" editable="bottom">
<field name="name" required="1" domain="[('type', '=', parent.name)]"/>
<field name="fn" select="1" />
<field name="field_id" select="1" required="1" domain="[('model_id', '=', parent.object_id)]" />
<field name="expr" />
</tree>
<form string="Attributes Mapping">
<field name="name" select="1" required="1" domain="[('type', '=', parent.name)]"/>
<field name="field_id" select="1" domain="[('model_id', '=', parent.object_id)]"
required="1" />
<field name="fn" select="1" required="1" />
<field name="expr" />
<separator string="Value Mapping" colspan="4" />
<field name="mapping" select="1" colspan="4" nolabel="1" />
</form>
</field>
</form>
<tree string="Attributes Mapping" editable="bottom">
<field name="name" select="1" />
<field name="object_id" select="1" />
</tree>
<form string="Calendar Lines">
<field name="name" required="1" select="1" />
<field name="object_id" required="1" select="1" />
<field name="domain" select="1" />
<field name="mapping_ids" select="1" colspan="4" nolabel="1">
<tree string="Attributes Mapping" editable="bottom">
<field name="name" required="1" domain="[('type', '=', parent.name)]"/>
<field name="fn" select="1" />
<field name="field_id" select="1" required="1" domain="[('model_id', '=', parent.object_id)]" />
<field name="expr" />
</tree>
<form string="Attributes Mapping">
<field name="name" select="1" required="1" domain="[('type', '=', parent.name)]"/>
<field name="field_id" select="1" domain="[('model_id', '=', parent.object_id)]"
required="1" />
<field name="fn" select="1" required="1" />
<field name="expr" />
<separator string="Value Mapping" colspan="4" />
<field name="mapping" select="1" colspan="4" nolabel="1" />
</form>
</field>
</form>
<tree string="Calendar Lines" editable="bottom">
<field name="name" select="1" />
<field name="object_id" select="1" />
</tree>
</field>
</form>
</field>
</record>
</form>
</field>
</record>
<record id="action_view_calendar" model="ir.actions.act_window">
<field name="name">Calendar</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">basic.calendar</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="base.menu_calendar_configuration" name="Calendar"
parent="base.menu_base_config" sequence="10" />
<record model="ir.ui.view" id="view_caldav_tree">
<field name="name">Calendar : Tree</field>
<field name="model">basic.calendar</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Calendars" toolbar="1">
<field name="name"/>
<field name="type"/>
<field name="user_id"/>
<field name="create_date"/>
<field name="write_date"/>
</tree>
</field>
</record>
<menuitem id="menu_calendar"
name="Calendar" parent="base.menu_calendar_configuration"
sequence="5" action="action_view_calendar" />
<record model="ir.actions.act_window" id="action_caldav_form">
<field name="name">Calendars</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">basic.calendar</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
</data>
<record id="action_caldav_view1" model="ir.actions.act_window.view">
<field eval="10" name="sequence"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_caldav_tree"/>
<field name="act_window_id" ref="action_caldav_form"/>
</record>
<record id="action_caldav_view2" model="ir.actions.act_window.view">
<field eval="20" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_caldav_form"/>
<field name="act_window_id" ref="action_caldav_form"/>
</record>
<menuitem
action="action_caldav_form"
id="menu_caldav_directories"
parent="menu_calendar"/>
</data>
</openerp>

1058
addons/caldav/calendar.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import osv, fields
from tools.translate import _
import caldav_node
class calendar_collection(osv.osv):
_inherit = 'document.directory'
_columns = {
'calendar_collection' : fields.boolean('Calendar Collection'),
'calendar_ids': fields.one2many('basic.calendar', 'collection_id', 'Calendars'),
}
_default = {
'calendar_collection' : False,
}
def _get_root_calendar_directory(self, cr, uid, context=None):
objid = self.pool.get('ir.model.data')
try:
mid = objid._get_id(cr, uid, 'document', 'dir_calendars')
if not mid:
return False
root_id = objid.read(cr, uid, mid, ['res_id'])['res_id']
root_cal_dir = self.browse(cr, uid, root_id, context=context)
return root_cal_dir.name
except Exception, e:
import netsvc
logger = netsvc.Logger()
logger.notifyChannel("document", netsvc.LOG_WARNING, 'Cannot set root directory for Calendars:'+ str(e))
return False
return False
def _locate_child(self, cr, uid, root_id, uri,nparent, ncontext):
""" try to locate the node in uri,
Return a tuple (node_dir, remaining_path)
"""
return (caldav_node.node_database(context=ncontext), uri)
def get_description(self, cr, uid, ids, context=None):
#TODO : return description of all calendars
if not context:
context = {}
return False
def get_schedule_inbox_URL(self, cr, uid, ids, context=None):
calendar_obj = self.pool.get('basic.calendar')
calendar_ids = calendar_obj.search(cr, uid, [
('user_id', '=', uid), ('collection_id', 'in', ids)
], limit=1, context=context)
root_cal_dir = self._get_root_calendar_directory(cr, uid, context=context)
if not calendar_ids:
return root_cal_dir
calendar_id = calendar_ids[0]
calendar = calendar_obj.browse(cr, uid, calendar_id,
context=context)
return '%s/%s' %(root_cal_dir, calendar.name)
def get_schedule_outbox_URL(self, cr, uid, ids, context=None):
return self.get_schedule_inbox_URL(cr, uid, ids, context=context)
calendar_collection()

View File

@ -1,5 +1,4 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_basic_calendar_all","basic.calendar","model_basic_calendar","base.group_user",1,1,1,1
"access_basic_calendar_event_all","basic.calendar.event","model_basic_calendar_event","base.group_user",1,1,1,0
"access_basic_calendar_attendee_all","basic.calendar.attendee","model_basic_calendar_attendee","base.group_user",1,1,1,0
"access_calendar_todo_all","basic.calendar.todo","model_basic_calendar_todo","base.group_user",1,1,1,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
access_basic_calendar_all basic.calendar model_basic_calendar base.group_user 1 1 1 1
2 access_basic_calendar_event_all basic.calendar.event model_basic_calendar_event base.group_user 1 1 1 0
3 access_basic_calendar_attendee_all basic.calendar.attendee model_basic_calendar_attendee base.group_user 1 1 1 0
4 access_calendar_todo_all basic.calendar.todo model_basic_calendar_todo base.group_user 1 1 1 0

View File

@ -1,78 +0,0 @@
# -*- encoding: utf-8 -*-
#
# Copyright P. Christeas <p_christ@hol.gr> 2008,2009
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
###############################################################################
import netsvc
from caldav_fs import tinydav_handler
from tools.config import config
from DAV.WebDAVServer import DAVRequestHandler
from service.websrv_lib import HTTPDir,FixSendError
class DAVHandler(FixSendError, DAVRequestHandler):
verbose = False
def get_userinfo(self, user, pw):
return False
def _log(self, message):
netsvc.Logger().notifyChannel("webdav", netsvc.LOG_DEBUG, message)
def handle(self):
pass
def finish(self):
pass
def setup(self):
davpath = '/calendar/'
self.baseuri = "http://%s:%d%s"% (self.server.server_name, self.server.server_port, davpath)
self.IFACE_CLASS = tinydav_handler(self)
pass
def log_message(self, format, *args):
netsvc.Logger().notifyChannel('webdav', netsvc.LOG_DEBUG_RPC, format % args)
def log_error(self, format, *args):
netsvc.Logger().notifyChannel('webdav', netsvc.LOG_WARNING, format % args)
try:
from service.http_server import reg_http_service, OpenERPAuthProvider
davpath = '/calendar/'
handler = DAVHandler
handler.verbose = config.get_misc('webdav', 'verbose', True)
handler.debug = config.get_misc('webdav', 'debug', True)
reg_http_service(HTTPDir(davpath, DAVHandler, OpenERPAuthProvider()))
netsvc.Logger().notifyChannel('webdav', netsvc.LOG_INFO, "WebDAV service registered at path: %s/ "% davpath)
except Exception, e:
logger = netsvc.Logger()
logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4

View File

@ -46,7 +46,7 @@
<field name="target">new</field>
</record>
<act_window id="action_calendar_event_import_values"
<act_window id="action_calendar_event_import_values"
key2="client_action_multi" name="Import .ics File"
res_model="calendar.event.import" src_model="basic.calendar"
view_mode="form" target="new" view_type="form" />

View File

@ -52,4 +52,4 @@
view_mode="form" target="new" view_type="form" />
</data>
</openerp>
</openerp>

View File

@ -24,13 +24,12 @@ import crm_mailgate
import crm_action_rule
import crm_segmentation
import crm_meeting
import crm_opportunity
import crm_lead
import crm_phonecall
import crm_claim
import crm_fundraising
import crm_helpdesk
import crm_opportunity
import crm_phonecall
import crm_installer
import report
import wizard

View File

@ -21,12 +21,12 @@
{
'name': 'Customer & Supplier Relationship Management',
'version': '1.0',
'category': 'Generic Modules/CRM & SRM',
'name': 'Customer & Supplier Relationship Management',
'version': '1.0',
'category': 'Generic Modules/CRM & SRM',
'description': """The generic Open ERP Customer Relationship Management
system enables a group of people to intelligently and efficiently manage
leads, opportunities, claims, meeting, phonecall etc.
leads, opportunities, meeting, phonecall etc.
It manages key tasks such as communication, identification, prioritization,
assignment, resolution and notification.
@ -41,86 +41,78 @@ appropriate staff, and making sure all future correspondence gets to the right
place.
The CRM module has a email gateway for the synchronisation interface
between mails and Open ERP.""",
'author': 'Tiny',
'website': 'http://www.openerp.com',
between mails and Open ERP.""",
'author': 'Tiny',
'website': 'http://www.openerp.com',
'depends': [
'base',
'base_action_rule',
'process',
'mail_gateway',
'base_calendar',
'resource',
],
'base_action_rule',
'process',
'mail_gateway',
'base_calendar',
'resource',
],
'init_xml': [
'crm_data.xml',
'crm_meeting_data.xml',
'crm_claims_data.xml',
'crm_fund_data.xml',
'crm_helpdesk_data.xml',
'crm_lead_data.xml',
'crm_meeting_data.xml',
'crm_opportunity_data.xml',
'crm_phonecall_data.xml',
],
'crm_data.xml',
'crm_meeting_data.xml',
'crm_lead_data.xml',
'crm_meeting_data.xml',
'crm_opportunity_data.xml',
'crm_phonecall_data.xml',
],
'update_xml': [
'wizard/crm_lead_to_partner_view.xml',
'wizard/crm_lead_to_opportunity_view.xml',
'wizard/crm_lead_to_partner_view.xml',
'wizard/crm_lead_to_opportunity_view.xml',
'wizard/crm_phonecall_to_phonecall_view.xml',
'wizard/crm_phonecall_to_partner_view.xml',
'wizard/crm_phonecall_to_opportunity_view.xml',
'wizard/crm_phonecall_to_phonecall_view.xml',
'wizard/crm_phonecall_to_partner_view.xml',
'wizard/crm_phonecall_to_opportunity_view.xml',
'wizard/crm_opportunity_to_phonecall_view.xml',
'wizard/crm_partner_to_opportunity_view.xml',
'wizard/crm_opportunity_to_phonecall_view.xml',
'wizard/crm_partner_to_opportunity_view.xml',
'wizard/crm_forward_to_partner_view.xml',
'wizard/crm_send_email_view.xml',
'wizard/crm_email_add_cc_view.xml',
'crm_view.xml',
'wizard/crm_forward_to_partner_view.xml',
'wizard/crm_send_email_view.xml',
'crm_view.xml',
'crm_action_rule_view.xml',
'crm_lead_view.xml',
'crm_lead_menu.xml',
'crm_meeting_view.xml',
'crm_meeting_menu.xml',
'crm_phonecall_view.xml',
'crm_phonecall_menu.xml',
'crm_opportunity_view.xml',
'crm_opportunity_menu.xml',
'crm_fund_view.xml',
'crm_fund_menu.xml',
'crm_claims_view.xml',
'crm_claims_menu.xml',
'crm_action_rule_view.xml',
'crm_lead_view.xml',
'crm_lead_menu.xml',
'crm_meeting_view.xml',
'crm_meeting_menu.xml',
'crm_phonecall_view.xml',
'crm_phonecall_menu.xml',
'crm_opportunity_view.xml',
'crm_opportunity_menu.xml',
'security/crm_security.xml',
'security/ir.model.access.csv',
'crm_helpdesk_view.xml',
'crm_helpdesk_menu.xml',
'report/crm_lead_report_view.xml',
'report/crm_phonecall_report_view.xml',
'security/crm_security.xml',
'security/ir.model.access.csv',
'report/crm_report_view.xml',
'report/crm_claim_report_view.xml',
'report/crm_lead_report_view.xml',
'report/crm_fundraising_report_view.xml',
'report/crm_opportunity_report_view.xml' ,
'report/crm_phonecall_report_view.xml',
'process/crm_configuration_process.xml',
],
'process/crm_configuration_process.xml',
'crm_installer_view.xml'
],
'demo_xml': [
'crm_demo.xml',
'crm_claims_demo.xml',
'crm_fund_demo.xml',
'crm_helpdesk_demo.xml',
'crm_lead_demo.xml',
'crm_meeting_demo.xml',
'crm_opportunity_demo.xml',
'crm_demo.xml',
'crm_lead_demo.xml',
'crm_meeting_demo.xml',
'crm_opportunity_demo.xml',
'crm_phonecall_demo.xml'
],
'installable': True,
'active': False,
'certificate': '0079056041421',
],
'test': [
'test/test_crm_lead.yml',
'test/test_crm_meeting.yml',
'test/test_crm_opportunity.yml',
'test/test_crm_phonecall.yml',
],
'installable': True,
'active': False,
'certificate': '0079056041421',
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -45,6 +45,273 @@ AVAILABLE_PRIORITIES = [
('5', 'Lowest'),
]
class crm_case(object):
"""A simple python class to be used for common functions """
def _get_default_partner_address(self, cr, uid, context):
"""Gives id of default address for current user
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values
"""
if not context.get('portal', False):
return False
return self.pool.get('res.users').browse(cr, uid, uid, context).address_id.id
def _get_default_partner(self, cr, uid, context):
"""Gives id of partner for current user
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values
"""
if not context.get('portal', False):
return False
user = self.pool.get('res.users').browse(cr, uid, uid, context)
if not user.address_id:
return False
return user.address_id.partner_id.id
def _get_default_email(self, cr, uid, context):
"""Gives default email address for current user
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values
"""
if not context.get('portal', False):
return False
user = self.pool.get('res.users').browse(cr, uid, uid, context)
if not user.address_id:
return False
return user.address_id.email
def _get_default_user(self, cr, uid, context):
"""Gives current user id
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values
"""
if context.get('portal', False):
return False
return uid
def _get_section(self, cr, uid, context):
"""Gives section id for current User
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values
"""
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
return user.context_section_id.id or False
def stage_next(self, cr, uid, ids, context=None):
"""This function computes next stage for case from its current stage
using available stage for that case type
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case IDs
@param context: A standard dictionary for contextual values"""
if not context:
context = {}
s = self.get_stage_dict(cr, uid, ids, context=context)
for case in self.browse(cr, uid, ids, context):
section = (case.section_id.id or False)
if section in s:
st = case.stage_id.id or False
if st in s[section]:
self.write(cr, uid, [case.id], {'stage_id': s[section][st]})
return True
def get_stage_dict(self, cr, uid, ids, context=None):
"""This function gives dictionary for stage according to stage levels
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case IDs
@param context: A standard dictionary for contextual values"""
if not context:
context = {}
sid = self.pool.get('crm.case.stage').search(cr, uid, \
[('object_id.model', '=', self._name)], context=context)
s = {}
previous = {}
for stage in self.pool.get('crm.case.stage').browse(cr, uid, sid, context=context):
section = stage.section_id.id or False
s.setdefault(section, {})
s[section][previous.get(section, False)] = stage.id
previous[section] = stage.id
return s
def stage_previous(self, cr, uid, ids, context=None):
"""This function computes previous stage for case from its current stage
using available stage for that case type
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case IDs
@param context: A standard dictionary for contextual values"""
if not context:
context = {}
s = self.get_stage_dict(cr, uid, ids, context=context)
for case in self.browse(cr, uid, ids, context):
section = (case.section_id.id or False)
if section in s:
st = case.stage_id.id or False
s[section] = dict([(v, k) for (k, v) in s[section].iteritems()])
if st in s[section]:
self.write(cr, uid, [case.id], {'stage_id': s[section][st]})
return True
def onchange_partner_id(self, cr, uid, ids, part, email=False):
"""This function returns value of partner address based on partner
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case IDs
@param part: Partner's id
@email: Partner's email ID
"""
if not part:
return {'value': {'partner_address_id': False,
'email_from': False,
}}
addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact'])
data = {'partner_address_id': addr['contact']}
data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value'])
return {'value': data}
def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
"""This function returns value of partner email based on Partner Address
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case IDs
@param add: Id of Partner's address
@email: Partner's email ID
"""
if not add:
return {'value': {'email_from': False}}
address = self.pool.get('res.partner.address').browse(cr, uid, add)
return {'value': {'email_from': address.email}}
def case_open(self, cr, uid, ids, *args):
"""Opens Case
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
self._history(cr, uid, cases, _('Open'))
for case in cases:
data = {'state': 'open', 'active': True}
if not case.user_id:
data['user_id'] = uid
self.write(cr, uid, case.id, data)
self._action(cr, uid, cases, 'open')
return True
def case_close(self, cr, uid, ids, *args):
"""Closes Case
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self._history(cr, uid, cases, _('Close'))
self.write(cr, uid, ids, {'state': 'done',
'date_closed': time.strftime('%Y-%m-%d %H:%M:%S'),
})
#
# We use the cache of cases to keep the old case state
#
self._action(cr, uid, cases, 'done')
return True
def case_escalate(self, cr, uid, ids, *args):
"""Escalates case to top level
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
for case in cases:
data = {'active': True, 'user_id': False}
if case.section_id.parent_id:
data['section_id'] = case.section_id.parent_id.id
if case.section_id.parent_id.user_id:
data['user_id'] = case.section_id.parent_id.user_id.id
else:
raise osv.except_osv(_('Error !'), _('You can not escalate this case.\nYou are already at the top level.'))
self.write(cr, uid, [case.id], data)
cases = self.browse(cr, uid, ids)
self._history(cr, uid, cases, _('Escalate'))
self._action(cr, uid, cases, 'escalate')
return True
def case_cancel(self, cr, uid, ids, *args):
"""Cancels Case
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self._history(cr, uid, cases, _('Cancel'))
self.write(cr, uid, ids, {'state': 'cancel',
'active': True})
self._action(cr, uid, cases, 'cancel')
return True
def case_pending(self, cr, uid, ids, *args):
"""Marks case as pending
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self._history(cr, uid, cases, _('Pending'))
self.write(cr, uid, ids, {'state': 'pending', 'active': True})
self._action(cr, uid, cases, 'pending')
return True
def case_reset(self, cr, uid, ids, *args):
"""Resets case as draft
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self._history(cr, uid, cases, _('Draft'))
self.write(cr, uid, ids, {'state': 'draft', 'active': True})
self._action(cr, uid, cases, 'draft')
return True
class crm_case_section(osv.osv):
"""Sales Team"""
@ -59,6 +326,7 @@ class crm_case_section(osv.osv):
true, it will allow you to hide the sales team without removing it."),
'allow_unlink': fields.boolean('Allow Delete', help="Allows to delete non draft cases"),
'user_id': fields.many2one('res.users', 'Responsible User'),
'member_ids':fields.many2many('res.users', 'sale_member_rel', 'section_id', 'member_id', 'Team Members'),
'reply_to': fields.char('Reply-To', size=64, help="The email address put \
in the 'Reply-To' of all emails sent by Open ERP about cases in this sales team"),
'parent_id': fields.many2one('crm.case.section', 'Parent Team'),
@ -78,15 +346,16 @@ class crm_case_section(osv.osv):
]
def _check_recursion(self, cr, uid, ids):
"""
Checks for recursion level for sections
Checks for recursion level for sales team
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of section ids
@param ids: List of Sales team ids
"""
level = 100
while len(ids):
cr.execute('select distinct parent_id from crm_case_section where id =ANY(%s)', (ids,))
ids = filter(None, map(lambda x: x[0], cr.fetchall()))
@ -97,7 +366,7 @@ class crm_case_section(osv.osv):
return True
_constraints = [
(_check_recursion, 'Error ! You cannot create recursive sections.', ['parent_id'])
(_check_recursion, 'Error ! You cannot create recursive Sales team.', ['parent_id'])
]
def name_get(self, cr, uid, ids, context=None):
@ -105,7 +374,7 @@ class crm_case_section(osv.osv):
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of section ids
@param ids: List of sales team ids
"""
if not context:
context = {}
@ -136,8 +405,8 @@ class crm_case_categ(osv.osv):
'section_id': fields.many2one('crm.case.section', 'Sales Team'),
'object_id': fields.many2one('ir.model', 'Object Name'),
}
def _find_object_id(self, cr, uid, context=None):
def _find_object_id(self, cr, uid, context=None):
"""Finds id for case object
@param self: The object pointer
@param cr: the current row, from the database cursor,
@ -151,8 +420,8 @@ class crm_case_categ(osv.osv):
_defaults = {
'object_id' : _find_object_id
}
}
crm_case_categ()
@ -238,501 +507,6 @@ def _links_get(self, cr, uid, context=None):
res = obj.read(cr, uid, ids, ['object', 'name'], context)
return [(r['object'], r['name']) for r in res]
class crm_case(osv.osv):
""" CRM Case """
_name = "crm.case"
_description = "Case"
def _email_last(self, cursor, user, ids, name, arg, context=None):
"""Return last email from History
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of cases IDs
@param context: A standard dictionary for contextual values
"""
res = {}
for case in self.browse(cursor, user, ids):
if case.history_line:
res[case.id] = case.history_line[0].description
else:
res[case.id] = False
return res
def copy(self, cr, uid, id, default=None, context=None):
"""Overrides orm copy method
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of cases IDs
@param context: A standard dictionary for contextual values
"""
if not context:
context = {}
if not default:
default = {}
default.update({'state': 'draft', 'id': False})
return super(crm_case, self).copy(cr, uid, id, default, context)
def _get_log_ids(self, cr, uid, ids, field_names, arg, context=None):
"""Gets id for case log from history of particular case
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of Case IDs
@param context: A standard dictionary for contextual values
"""
if not context:
context = {}
result = {}
history_obj = False
model_obj = self.pool.get('ir.model')
if 'history_line' in field_names:
history_obj = self.pool.get('crm.case.history')
name = 'history_line'
if 'log_ids' in field_names:
history_obj = self.pool.get('crm.case.log')
name = 'log_ids'
if not history_obj:
return result
for case in self.browse(cr, uid, ids, context):
model_ids = model_obj.search(cr, uid, [('model', '=', case._name)])
history_ids = history_obj.search(cr, uid, [('model_id', '=', model_ids[0]), \
('res_id', '=', case.id)])
if history_ids:
result[case.id] = {name: history_ids}
else:
result[case.id] = {name: []}
return result
_columns = {
'id': fields.integer('ID', readonly=True),
'name': fields.char('Description', size=1024, required=True),
'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the case without removing it."),
'description': fields.text('Description'),
'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.'),
'email_from': fields.char('Email', size=128, help="These people will receive email."),
'email_cc': fields.text('Watchers Emails', size=252 , help="These people\
will receive a copy of the future" \
" communication between partner and users by email"),
'probability': fields.float('Probability'),
'email_last': fields.function(_email_last, method=True,
string='Latest E-Mail', type='text'),
'partner_id': fields.many2one('res.partner', 'Partner'),
'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \
domain="[('partner_id','=',partner_id)]"),
'create_date': fields.datetime('Creation Date' , readonly=True),
'write_date': fields.datetime('Update Date' , readonly=True),
'date_deadline': fields.date('Deadline'),
'user_id': fields.many2one('res.users', 'Responsible'),
'history_line': fields.function(_get_log_ids, method=True, type='one2many', \
multi="history_line", relation="crm.case.history", string="Communication"),
'log_ids': fields.function(_get_log_ids, method=True, type='one2many', \
multi="log_ids", relation="crm.case.log", string="Logs History"),
'stage_id': fields.many2one ('crm.case.stage', 'Stage', \
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.opportunity')]"),
'state': fields.selection(AVAILABLE_STATES, 'State', size=16, readonly=True,
help='The state is set to \'Draft\', when a case is created.\
\nIf the case is in progress the state is set to \'Open\'.\
\nWhen the case is over, the state is set to \'Done\'.\
\nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
'company_id': fields.many2one('res.company', 'Company'),
}
def _get_default_partner_address(self, cr, uid, context):
"""Gives id of default address for current user
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values
"""
if not context.get('portal', False):
return False
return self.pool.get('res.users').browse(cr, uid, uid, context).address_id.id
def _get_default_partner(self, cr, uid, context):
"""Gives id of partner for current user
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values
"""
if not context.get('portal', False):
return False
user = self.pool.get('res.users').browse(cr, uid, uid, context)
if not user.address_id:
return False
return user.address_id.partner_id.id
def _get_default_email(self, cr, uid, context):
"""Gives default email address for current user
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values
"""
if not context.get('portal', False):
return False
user = self.pool.get('res.users').browse(cr, uid, uid, context)
if not user.address_id:
return False
return user.address_id.email
def _get_default_user(self, cr, uid, context):
"""Gives current user id
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values
"""
if context.get('portal', False):
return False
return uid
def _get_section(self, cr, uid, context):
"""Gives section id for current User
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values
"""
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
return user.context_section_id.id or False
_defaults = {
'active': lambda *a: 1,
'user_id': _get_default_user,
'partner_id': _get_default_partner,
'partner_address_id': _get_default_partner_address,
'email_from': _get_default_email,
'state': lambda *a: 'draft',
'section_id': _get_section,
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c),
}
_order = 'date_deadline desc, create_date desc,id desc'
def unlink(self, cr, uid, ids, context=None):
"""Overrides orm unlink method
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case IDs
@param context: A standard dictionary for contextual values
"""
if not context:
context = {}
hist_obj = self.pool.get('crm.case.history')
log_obj = self.pool.get('crm.case.log')
for case in self.browse(cr, uid, ids, context):
if (not case.section_id.allow_unlink) and (case.state <> 'draft'):
raise osv.except_osv(_('Warning !'),
_('You can not delete this case. You should better cancel it.'))
# Also removing history and logs
history_ids = map(lambda x: x.id, case.history_line)
log_ids = map(lambda x: x.id, case.log_ids)
hist_obj.unlink(cr, uid, history_ids, context=context)
log_obj.unlink(cr, uid, log_ids, context=context)
return super(crm_case, self).unlink(cr, uid, ids, context)
def stage_next(self, cr, uid, ids, context=None):
"""This function computes next stage for case from its current stage
using available stage for that case type
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case IDs
@param context: A standard dictionary for contextual values"""
if not context:
context = {}
s = self.get_stage_dict(cr, uid, ids, context=context)
for case in self.browse(cr, uid, ids, context):
section = (case.section_id.id or False)
if section in s:
st = case.stage_id.id or False
if st in s[section]:
self.write(cr, uid, [case.id], {'stage_id': s[section][st]})
return True
def get_stage_dict(self, cr, uid, ids, context=None):
"""This function gives dictionary for stage according to stage levels
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case IDs
@param context: A standard dictionary for contextual values"""
if not context:
context = {}
sid = self.pool.get('crm.case.stage').search(cr, uid, \
[('object_id.model', '=', self._name)], context=context)
s = {}
previous = {}
for stage in self.pool.get('crm.case.stage').browse(cr, uid, sid, context=context):
section = stage.section_id.id or False
s.setdefault(section, {})
s[section][previous.get(section, False)] = stage.id
previous[section] = stage.id
return s
def stage_previous(self, cr, uid, ids, context=None):
"""This function computes previous stage for case from its current stage
using available stage for that case type
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case IDs
@param context: A standard dictionary for contextual values"""
if not context:
context = {}
s = self.get_stage_dict(cr, uid, ids, context=context)
for case in self.browse(cr, uid, ids, context):
section = (case.section_id.id or False)
if section in s:
st = case.stage_id.id or False
s[section] = dict([(v, k) for (k, v) in s[section].iteritems()])
if st in s[section]:
self.write(cr, uid, [case.id], {'stage_id': s[section][st]})
return True
def __history(self, cr, uid, cases, keyword, history=False, email=False, details=None, email_from=False, message_id=False, context={}):
"""
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param cases: a browse record list
@param keyword: Case action keyword e.g.: If case is closed "Close" keyword is used
@param history: Value True/False, If True it makes entry in case History otherwise in Case Log
@param email: Email address if any
@param details: Details of case history if any
@param context: A standard dictionary for contextual values"""
if not context:
context = {}
# The mailgate sends the ids of the cases and not the object list
if all(isinstance(case_id, (int, long)) for case_id in cases) and context.get('model'):
cases = self.pool.get(context['model']).browse(cr, uid, cases, context=context)
model_obj = self.pool.get('ir.model')
obj = self.pool.get('crm.case.log')
for case in cases:
model_ids = model_obj.search(cr, uid, [('model', '=', case._name)])
data = {
'name': keyword,
'user_id': uid,
'date': time.strftime('%Y-%m-%d %H:%M:%S'),
'model_id' : model_ids and model_ids[0] or False,
'res_id': case.id,
'section_id': case.section_id.id,
'message_id':message_id
}
if history:
obj = self.pool.get('crm.case.history')
data['description'] = details or case.description
data['email_to'] = email or \
(case.section_id and case.section_id.reply_to) or \
(case.user_id and case.user_id.address_id and \
case.user_id.address_id.email) or tools.config.get('email_from', False)
data['email_from'] = email_from or \
(case.section_id and case.section_id.reply_to) or \
(case.user_id and case.user_id.address_id and \
case.user_id.address_id.email) or tools.config.get('email_from', False)
res = obj.create(cr, uid, data, context)
return True
_history = __history
history = __history
def create(self, cr, uid, *args, **argv):
"""Overrides orm create method
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param *args: Tuple Value for additional Params
@param **argv: Dictionay of Keyword Params
"""
res = super(crm_case, self).create(cr, uid, *args, **argv)
cases = self.browse(cr, uid, [res])
cases[0].state # to fill the browse record cache
self._action(cr, uid, cases, 'draft')
return res
def add_reply(self, cursor, user, ids, context=None):
"""This function finds last email and gives its description value for reply mail
@param self: The object pointer
@param cursor: the current row, from the database cursor,
@param user: the current users ID for security checks
@param ids: List of case IDs
@param context: A standard dictionary for contextual values
"""
for case in self.browse(cursor, user, ids, context=context):
if case.email_last:
description = case.email_last
self.write(cursor, user, case.id, {
'description': '> ' + description.replace('\n', '\n> '),
}, context=context)
return True
def onchange_partner_id(self, cr, uid, ids, part, email=False):
"""This function returns value of partner address based on partner
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case IDs
@param part: Partner's id
@email: Partner's email ID
"""
if not part:
return {'value': {'partner_address_id': False,
'email_from': False,
}}
addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact'])
data = {'partner_address_id': addr['contact']}
data.update(self.onchange_partner_address_id(cr, uid, ids, addr['contact'])['value'])
return {'value': data}
def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
"""This function returns value of partner email based on Partner Address
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case IDs
@param add: Id of Partner's address
@email: Partner's email ID
"""
if not add:
return {'value': {'email_from': False}}
address = self.pool.get('res.partner.address').browse(cr, uid, add)
return {'value': {'email_from': address.email}}
def case_close(self, cr, uid, ids, *args):
"""Closes Case
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self.__history(cr, uid, cases, _('Close'))
self.write(cr, uid, ids, {'state': 'done',
'date_closed': time.strftime('%Y-%m-%d %H:%M:%S'),
'probability' : 100.0})
#
# We use the cache of cases to keep the old case state
#
self._action(cr, uid, cases, 'done')
return True
def case_escalate(self, cr, uid, ids, *args):
"""Escalates case to top level
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
for case in cases:
data = {'active': True, 'user_id': False}
if case.section_id.parent_id:
data['section_id'] = case.section_id.parent_id.id
if case.section_id.parent_id.user_id:
data['user_id'] = case.section_id.parent_id.user_id.id
else:
raise osv.except_osv(_('Error !'), _('You can not escalate this case.\nYou are already at the top level.'))
self.write(cr, uid, [case.id], data)
cases = self.browse(cr, uid, ids)
self.__history(cr, uid, cases, _('Escalate'))
self._action(cr, uid, cases, 'escalate')
return True
def case_open(self, cr, uid, ids, *args):
"""Opens Case
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
self.__history(cr, uid, cases, _('Open'))
for case in cases:
data = {'state': 'open', 'active': True}
if not case.user_id:
data['user_id'] = uid
self.write(cr, uid, case.id, data)
self._action(cr, uid, cases, 'open')
return True
def case_cancel(self, cr, uid, ids, *args):
"""Cancels Case
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self.__history(cr, uid, cases, _('Cancel'))
self.write(cr, uid, ids, {'state': 'cancel',
'active': True,
'probability' : 0.0})
self._action(cr, uid, cases, 'cancel')
return True
def case_pending(self, cr, uid, ids, *args):
"""Marks case as pending
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self.__history(cr, uid, cases, _('Pending'))
self.write(cr, uid, ids, {'state': 'pending', 'active': True})
self._action(cr, uid, cases, 'pending')
return True
def case_reset(self, cr, uid, ids, *args):
"""Resets case as draft
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self.__history(cr, uid, cases, _('Draft'))
self.write(cr, uid, ids, {'state': 'draft', 'active': True})
self._action(cr, uid, cases, 'draft')
return True
crm_case()
class crm_case_log(osv.osv):
""" Case Communication History """
@ -743,7 +517,7 @@ class crm_case_log(osv.osv):
_columns = {
'name': fields.char('Status', size=64),
'date': fields.datetime('Date'),
'section_id': fields.many2one('crm.case.section', 'Section'),
'section_id': fields.many2one('crm.case.section', 'Sales Team'),
'user_id': fields.many2one('res.users', 'User Responsible', readonly=True),
'model_id': fields.many2one('ir.model', "Model"),
'res_id': fields.integer('Resource ID'),

View File

@ -37,7 +37,7 @@ import crm
class case(osv.osv):
""" Case """
_inherit = 'crm.case'
_inherit = 'mailgate.thread'
_description = 'case'
_columns = {
@ -89,7 +89,7 @@ class case(osv.osv):
attach_to_send = None
if attach:
attach_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', 'crm.case'), ('res_id', '=', case.id)])
attach_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', 'mailgate.thread'), ('res_id', '=', case.id)])
attach_to_send = self.pool.get('ir.attachment').read(cr, uid, attach_ids, ['datas_fname','datas'])
attach_to_send = map(lambda x: (x['datas_fname'], base64.decodestring(x['datas'])), attach_to_send)
@ -152,6 +152,18 @@ class base_action_rule(osv.osv):
""" Base Action Rule """
_inherit = 'base.action.rule'
_description = 'Action Rules'
_columns = {
'trg_section_id': fields.many2one('crm.case.section', 'Sales Team'),
'trg_max_history': fields.integer('Maximum Communication History'),
'trg_categ_id': fields.many2one('crm.case.categ', 'Category'),
'regex_history' : fields.char('Regular Expression on Case History', size=128),
'act_section_id': fields.many2one('crm.case.section', 'Set Team to'),
'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'),
'act_mail_to_partner': fields.boolean('Mail to partner',help="Check \
this if you want the rule to send an email to the partner."),
}
def email_send(self, cr, uid, obj, emails, body, emailfrom=tools.config.get('email_from',False), context={}):
body = self.format_mail(obj, body)
@ -183,16 +195,18 @@ class base_action_rule(osv.osv):
ok = ok and (not action.trg_section_id or action.trg_section_id.id==obj.section_id.id)
if hasattr(obj, 'categ_id'):
ok = ok and (not action.trg_categ_id or action.trg_categ_id.id==obj.categ_id.id)
if hasattr(obj, 'history_line'):
ok = ok and (not action.trg_max_history or action.trg_max_history<=(len(obj.history_line)+1))
reg_history = action.regex_history
result_history = True
if reg_history:
ptrn = re.compile(str(reg_history))
if obj.history_line:
_result = ptrn.search(str(obj.history_line[0].description))
if not _result:
result_history = False
# TODO: history_line is removed
# if hasattr(obj, 'history_line'):
# ok = ok and (not action.trg_max_history or action.trg_max_history<=(len(obj.history_line)+1))
# reg_history = action.regex_history
# result_history = True
# if reg_history:
# ptrn = re.compile(str(reg_history))
# if obj.history_line:
# _result = ptrn.search(str(obj.history_line[0].description))
# if not _result:
# result_history = False
regex_h = not reg_history or result_history
ok = ok and regex_h
return ok
@ -207,7 +221,7 @@ class base_action_rule(osv.osv):
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:
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
@ -231,13 +245,6 @@ class base_action_rule(osv.osv):
return True
base_action_rule()
class base_action_rule_line(osv.osv):
""" Base Action Rule Line """
_inherit = 'base.action.rule.line'
_description = 'Base Action Rule Line'
def state_get(self, cr, uid, context={}):
"""@param self: The object pointer
@ -245,7 +252,7 @@ class base_action_rule_line(osv.osv):
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values """
res = super(base_action_rule_line, self).state_get(cr, uid, context=context)
res = super(base_action_rule, self).state_get(cr, uid, context=context)
return res + [('escalate','Escalate')] + crm.AVAILABLE_STATES
def priority_get(self, cr, uid, context={}):
@ -255,18 +262,10 @@ class base_action_rule_line(osv.osv):
@param uid: the current users ID for security checks,
@param context: A standard dictionary for contextual values """
res = super(base_action_rule_line, self).priority_get(cr, uid, context=context)
res = super(base_action_rule, self).priority_get(cr, uid, context=context)
return res + crm.AVAILABLE_PRIORITIES
_columns = {
'trg_section_id': fields.many2one('crm.case.section', 'Sales Team'),
'trg_max_history': fields.integer('Maximum Communication History'),
'trg_categ_id': fields.many2one('crm.case.categ', 'Category'),
'regex_history' : fields.char('Regular Expression on Case History', size=128),
'act_section_id': fields.many2one('crm.case.section', 'Set Team to'),
'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'),
'act_mail_to_partner': fields.boolean('Mail to partner',help="Check this \
if you want the rule to send an email to the partner."),
}
base_action_rule_line()
base_action_rule()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -3,9 +3,9 @@
<data>
<!-- Case rules -->
<record id="view_base_action_rule_line_form1" model="ir.ui.view">
<field name="name">base.action.rule.line.form.inherit</field>
<field name="model">base.action.rule.line</field>
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_line_form"/>
<field name="name">base.action.rule.form.inherit</field>
<field name="model">base.action.rule</field>
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<group name="partner" position="after">
@ -19,9 +19,9 @@
</record>
<record id="view_base_action_rule_line_form2" model="ir.ui.view">
<field name="name">base.action.rule.line.form2.inherit</field>
<field name="model">base.action.rule.line</field>
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_line_form"/>
<field name="name">base.action.rule.form2.inherit</field>
<field name="model">base.action.rule</field>
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<group name="partner" position="after">
@ -35,17 +35,29 @@
</record>
<record id="view_base_action_rule_line_form3" model="ir.ui.view">
<field name="name">base.action.rule.line.form3.inherit</field>
<field name="model">base.action.rule.line</field>
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_line_form"/>
<field name="name">base.action.rule.form3.inherit</field>
<field name="model">base.action.rule</field>
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="act_user_id" position="after">
<field name="act_section_id"/>
<field name="act_categ_id"/>
</field>
</field>
</record>
<record id="view_base_action_rule_line_form4" model="ir.ui.view">
<field name="name">base.action.rule.form4.inherit</field>
<field name="model">base.action.rule</field>
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="act_mail_to_watchers" position="before">
<field name="act_mail_to_partner"/>
</field>
</field>
</record>
</data>
</openerp>

View File

@ -1,73 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import fields, osv
import crm
class crm_claim(osv.osv):
"""
Crm claim
"""
_name = "crm.claim"
_description = "Claim Cases"
_order = "id desc"
_inherit = 'crm.case'
_columns = {
'date_closed': fields.datetime('Closed', readonly=True),
'date': fields.datetime('Date'),
'ref' : fields.reference('Reference', selection=crm._links_get, size=128),
'ref2' : fields.reference('Reference 2', selection=crm._links_get, size=128),
'canal_id': fields.many2one('res.partner.canal', 'Channel',\
help="The channels represent the different communication\
modes available with the customer." \
" With each commercial opportunity, you can indicate the\
canall which is this opportunity source."),
'planned_revenue': fields.float('Planned Revenue'),
'planned_cost': fields.float('Planned Costs'),
'som': fields.many2one('res.partner.som', 'State of Mind', \
help="The minds states allow to define a value scale which represents" \
"the partner mentality in relation to our services.The scale has" \
"to be created with a factor for each level from 0 (Very dissatisfied) \
to 10 (Extremely satisfied)."),
'categ_id': fields.many2one('crm.case.categ', 'Category',\
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.claim')]"),
'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
'type_id': fields.many2one('crm.case.resource.type', 'Claim Type',\
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.claim')]"),
'partner_name': fields.char("Employee's Name", size=64),
'partner_mobile': fields.char('Mobile', size=32),
'partner_phone': fields.char('Phone', size=32),
'stage_id': fields.many2one ('crm.case.stage', 'Stage',\
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.claim')]"),
'probability': fields.float('Probability (%)'),
}
_defaults = {
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
}
crm_claim()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -2,11 +2,6 @@
<openerp>
<data>
<record id="req_link_case" model="res.request.link">
<field name="name">Case</field>
<field name="object">crm.case</field>
</record>
<record id="event_type_case_create" model="res.partner.event.type">
<field name="name">CRM: Create Case</field>
<field name="key">crm_case_draft</field>

View File

@ -1,296 +0,0 @@
<?xml version="1.0"?>
<openerp>
<data>
<!-- Fund Raising Categories Form View -->
<record id="crm_fund_categ_action" model="ir.actions.act_window">
<field name="name">Fundraising Categories</field>
<field name="res_model">crm.case.categ</field>
<field name="view_type">form</field>
<field name="view_id" ref="crm.crm_case_categ_tree-view"/>
<field name="domain">[('object_id.model', '=', 'crm.fundraising')]</field>
<field name="context">{'object_id':'crm.fundraising'}</field>
</record>
<menuitem action="crm_fund_categ_action"
id="menu_crm_case_fundraising-act"
groups="base.group_extended"
parent="crm.menu_crm_case_categ" />
<!-- Fund Stage Form View -->
<record id="crm_fundraising_stage_act" model="ir.actions.act_window">
<field name="name">Fundraising Stages</field>
<field name="res_model">crm.case.stage</field>
<field name="view_type">form</field>
<field name="view_id" ref="crm.crm_case_stage_tree"/>
<field name="domain">[('object_id.model', '=', 'crm.fundraising')]</field>
<field name="context">{'object_id':'crm.fundraising'}</field>
</record>
<menuitem action="crm_fundraising_stage_act"
groups="base.group_extended"
id="menu_crm_fundraising_stage_act"
parent="crm.menu_crm_case_stage" />
<!-- Fund Resource Type Form View -->
<record id="crm_fundraising_resource_act" model="ir.actions.act_window">
<field name="name">Fundraising Resource Type</field>
<field name="res_model">crm.case.resource.type</field>
<field name="view_type">form</field>
<field name="view_id" ref="crm.crm_case_resource_type_tree"/>
<field name="domain">[('object_id.model', '=', 'crm.fundraising')]</field>
<field name="context">{'object_id':'crm.fundraising'}</field>
</record>
<menuitem action="crm_fundraising_resource_act"
groups="base.group_extended"
id="menu_crm_fundraising_resource_act"
parent="crm.menu_crm_case_resource_type" />
<!-- Fund Raising Tree View -->
<record model="ir.ui.view" id="crm_case_tree_view_fund">
<field name="name">CRM - Funds Tree</field>
<field name="model">crm.fundraising</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Funds Tree"
colors="red:state=='open';black:state in ('draft', 'cancel','done','pending')">
<field name="id" />
<field name="name" string="Fund Description" />
<field name="categ_id" />
<field name="type_id" string="Payment Mode" />
<field name="planned_cost" string="Amount" />
<field name="probability" />
<field name="user_id" />
<field name="state" />
<button name="case_close" string="Done"
states="open,draft,pending" type="object"
icon="gtk-close" />
<button name="case_open" string="Open"
states="draft,pending" type="object"
icon="gtk-go-forward" />
<button name="case_cancel" string="Cancel"
states="draft,open,pending" type="object"
icon="gtk-cancel" />
<button name="case_pending" string="Pending"
states="draft,open" type="object" icon="gtk-media-pause" />
<button name="case_escalate" string="Escalate"
states="open,draft,pending" type="object"
groups="base.group_extended"
icon="gtk-go-up" />
<button name="case_reset" string="Reset to Draft"
states="done,cancel" type="object" icon="gtk-convert" />
</tree>
</field>
</record>
<!-- Fund Raising Form View -->
<record model="ir.ui.view" id="crm_case_form_view_fund">
<field name="name">CRM - Funds Form</field>
<field name="model">crm.fundraising</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Funds Form">
<group colspan="4" col="4">
<field name="name" select="1" string="Name"/>
<field name="type_id" string="Payment Mode" select="1" widget="selection"
domain="[('object_id.model', '=', 'crm.fundraising')]" />
</group>
<notebook colspan="4">
<page string="Funds">
<separator colspan="4" string="Communication"/>
<group colspan="4" col="4">
<field name="partner_id" select="1"
on_change="onchange_partner_id(partner_id, email_from)"
colspan="2" />
<field name="partner_address_id"
string="Contact"
on_change="onchange_partner_address_id(partner_address_id, email_from)"
colspan="1" />
<field name="email_from" colspan="2"/>
</group>
<separator colspan="4" string="Estimates"/>
<field name="planned_cost"/>
<field name="planned_revenue"/>
<field name="probability"/>
<separator colspan="4" string="Categorization"/>
<group colspan="4" col="6">
<field name="user_id" string="Responsible"/>
<field name="section_id" colspan="1" widget="selection"/>
<field name="categ_id" select="1"
widget="selection"
domain="[('object_id.model', '=', 'crm.fundraising')]" />
<field name="date"/>
<field name="duration"/>
</group>
<separator colspan="4" string="Notes"/>
<field name="description" nolabel="1" colspan="4"/>
<separator colspan="4"/>
<group col="8" colspan="4">
<field name="state"/>
<button name="case_close" string="Done"
states="open,draft,pending" type="object"
icon="gtk-close" />
<button name="case_open" string="Open"
states="draft,pending" type="object"
icon="gtk-go-forward" />
<button name="case_cancel" string="Cancel"
states="draft,open,pending" type="object"
icon="gtk-cancel" />
<button name="case_pending" string="Pending"
states="draft,open" type="object"
icon="gtk-media-pause" />
<button name="case_escalate" string="Escalate"
states="open,draft,pending" type="object"
groups="base.group_extended"
icon="gtk-go-up" />
<button name="case_reset"
string="Reset to Draft" states="done,cancel"
type="object" icon="gtk-convert" />
</group>
</page>
<page string="History" groups="base.group_extended">
<field name="id" select="1"/>
<field name="active"/>
<field name="priority" string="Priority"/>
<newline/>
<field name="canal_id"/>
<field name="som"/>
<separator colspan="4" string="Dates"/>
<field name="create_date"/>
<field name="date_closed"/>
<separator colspan="4" string="References"/>
<field name="ref" colspan="4"/>
<field name="ref2" colspan="4"/>
<field name="log_ids" nolabel="1" colspan="4">
<form string="Actions">
<separator string="Action Information" colspan="4"/>
<field name="name" colspan="4"/>
<field name="date"/>
<field name="user_id"/>
</form>
</field>
</page>
<page string="Emails" groups="base.group_extended">
<group colspan="4">
<field colspan="4" name="email_cc" string="CC"/>
</group>
<field name="history_line" colspan="4" nolabel="1" mode="form,tree">
<form string="Communication history">
<group col="7" colspan="4">
<field name="date"/>
<field name="email_to"/>
<field name="email_from"/>
<button
string="Add a CC"
name="%(crm.action_view_crm_email_add_cc_wizard)d"
icon="gtk-add" type="action"/>
</group>
<newline/>
<field name="description" colspan="4" nolabel="1"/>
<button colspan="4"
string="Reply to Last Email"
name="%(action_crm_send_mail)d"
context="{'mail':'reply', 'model': 'crm.fundraising'}"
icon="gtk-undo" type="action" />
</form>
<tree string="Communication history">
<field name="description"/>
<field name="email_to"/>
<field name="date"/>
</tree>
</field>
<button colspan="4" string="Send New Email"
name="%(action_crm_send_mail)d"
context="{'mail':'new', 'model': 'crm.fundraising'}"
icon="gtk-go-forward" type="action" />
</page>
</notebook>
</form>
</field>
</record>
<!-- Fund Raising Calendar View -->
<record model="ir.ui.view" id="crm_case_calendar_view_fund">
<field name="name">CRM - Funds Calendar</field>
<field name="model">crm.fundraising</field>
<field name="type">calendar</field>
<field name="priority" eval="2"/>
<field name="arch" type="xml">
<calendar string="Funds" date_start="date" color="user_id" date_delay="duration">
<field name="name"/>
<field name="partner_id"/>
</calendar>
</field>
</record>
<!-- Fund Raising Graph View -->
<record model="ir.ui.view" id="crm_case_graph_view_fund">
<field name="name">CRM - Funds Graph</field>
<field name="model">crm.fundraising</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<graph string="Funds by Categories" type="bar" orientation="horizontal">
<field name="categ_id"/>
<field name="planned_cost" operator="+"/>
<field name="state" group="True"/>
</graph>
</field>
</record>
<!-- Fund Raising Search View -->
<record id="view_crm_case_fund_filter" model="ir.ui.view">
<field name="name">CRM - Funds Search</field>
<field name="model">crm.fundraising</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Search Funds">
<group col='6' colspan='4'>
<filter icon="terp-partner" string="My Funds"
default="1" domain="[('user_id','=',uid)]"
separator="1"
help="Funds Related to Current User"
/>
<separator orientation="vertical" />
<filter icon="gtk-new" string="Current Funds"
domain="[('state','in',('draft', 'open'))]"
help="Current Funds" />
<filter icon="gtk-yes" string="Open Funds"
domain="[('state','=','open')]"
help="Open Funds" />
<filter icon="gtk-media-pause"
string="Pending Funds"
domain="[('state','=','pending')]"
help="Pending Funds" />
<separator orientation="vertical" />
<field name="state" select="1" />
<field name="name" select='1'
string="Fund Description" />
<field name="user_id" select="1"
widget="selection">
<filter icon="terp-partner"
domain="[('user_id','=', False)]"
help="Unassigned" />
</field>
<field name="section_id" select="1" widget="selection" string="Sales Team">
<filter icon="terp-crm"
domain="[('section_id','=',context.get('section_id',False))]"
help="My section" />
</field>
</group>
</search>
</field>
</record>
</data>
</openerp>

View File

@ -1,68 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import fields
from osv import orm
from osv import osv
import crm
class crm_helpdesk(osv.osv):
""" Helpdesk Cases """
_name = "crm.helpdesk"
_description = "Helpdesk Cases"
_order = "id desc"
_inherit = 'crm.case'
_columns = {
'date_closed': fields.datetime('Closed', readonly=True),
'date': fields.datetime('Date'),
'ref' : fields.reference('Reference', selection=crm._links_get, size=128),
'ref2' : fields.reference('Reference 2', selection=crm._links_get, size=128),
'canal_id': fields.many2one('res.partner.canal', 'Channel', \
help="The channels represent the different communication \
modes available with the customer." \
" With each commercial opportunity, you can indicate\
the canall which is this opportunity source."),
'planned_revenue': fields.float('Planned Revenue'),
'planned_cost': fields.float('Planned Costs'),
'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
'probability': fields.float('Probability (%)'),
'som': fields.many2one('res.partner.som', 'State of Mind', \
help="The minds states allow to define a value scale which represents" \
"the partner mentality in relation to our services.The scale has" \
"to be created with a factor for each level from 0 \
(Very dissatisfied) to 10 (Extremely satisfied)."),
'categ_id': fields.many2one('crm.case.categ', 'Category', \
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.helpdesk')]"),
'duration': fields.float('Duration'),
}
_defaults = {
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
}
crm_helpdesk()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,249 +0,0 @@
<?xml version="1.0"?>
<openerp>
<data>
<!-- Helpdesk Support Categories Form View -->
<record id="crm_helpdesk_categ_action" model="ir.actions.act_window">
<field name="name">Helpdesk Categories</field>
<field name="res_model">crm.case.categ</field>
<field name="view_type">form</field>
<field name="view_id" ref="crm.crm_case_categ_tree-view"/>
<field name="domain">[('object_id.model', '=', 'crm.helpdesk')]</field>
<field name="context">{'object_id':'crm.helpdesk'}</field>
</record>
<menuitem action="crm_helpdesk_categ_action"
id="menu_crm_case_helpdesk-act" parent="crm.menu_crm_case_categ" />
<!-- Helpdesk Support Form View -->
<record model="ir.ui.view" id="crm_case_form_view_helpdesk">
<field name="name">CRM - Helpdesk Support Form</field>
<field name="model">crm.helpdesk</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Helpdesk Support">
<group colspan="4" col="4">
<field colspan="4" name="name" select="1"/>
<field name="date" select="1"/>
<field name="date_deadline"/>
</group>
<notebook colspan="4">
<page string="General">
<separator colspan="4" string="Communication"/>
<group col="7" colspan="4">
<field name="partner_id"
on_change="onchange_partner_id(partner_id, email_from)"
select="1" />
<field name="partner_address_id"
on_change="onchange_partner_address_id(partner_address_id, email_from)"
/>
<field name="email_from" />
<button name="remind_partner"
states="open,pending"
string="Send Reminder" type="object"
icon="gtk-go-forward" />
</group>
<separator colspan="4" string="Categorization"/>
<group col="7" colspan="4">
<field name="priority"/>
<field name="section_id" widget="selection"/>
<field name="user_id" select="1"/>
<button name="remind_user"
states="open,pending"
string="Send Reminder" type="object"
icon="gtk-go-forward" />
</group>
<separator colspan="4" string="Notes"/>
<field name="description" colspan="4"
nolabel="1" widget="text_tag" readonly="1" />
<separator colspan="4"/>
<group col="8" colspan="4">
<field name="state" select="1"/>
<button name="case_close"
states="open,draft,pending" string="Close"
type="object" icon="gtk-close" />
<button name="case_open"
states="draft,pending" string="Open"
type="object" icon="gtk-go-forward" />
<button name="case_cancel"
states="draft,open,pending" string="Cancel"
type="object" icon="gtk-cancel" />
<button name="case_pending"
states="draft,open" string="Pending"
type="object" icon="gtk-media-pause" />
<button name="case_escalate"
states="open,draft,pending"
string="Escalate" type="object"
icon="gtk-go-up" />
<button name="case_reset"
states="done,cancel"
string="Reset to Draft" type="object"
icon="gtk-convert" />
</group>
</page>
<page string="History" groups="base.group_extended">
<field name="id" select="1"/>
<field name="active"/>
<field name="categ_id"/>
<field name="som"/>
<separator colspan="4" string="Dates"/>
<field name="create_date"/>
<field name="date_closed"/>
<separator colspan="4" string="Estimates"/>
<field name="planned_cost"/>
<separator colspan="4" string="References"/>
<field colspan="4" name="ref"/>
<field colspan="4" name="ref2"/>
<field colspan="4" name="log_ids" nolabel="1">
<form string="Actions">
<separator colspan="4" string="Action Information"/>
<field colspan="4" name="name"/>
<field name="date"/>
<field name="user_id"/>
</form>
</field>
</page>
<page string="Emails" groups="base.group_extended">
<group colspan="4">
<field colspan="4" name="email_cc" string="CC"/>
</group>
<field name="history_line" colspan="4" nolabel="1" mode="form,tree">
<form string="Communication history">
<group col="7" colspan="4">
<field name="date"/>
<field name="email_to"/>
<field name="email_from"/>
<button
string="Add a CC"
name="%(crm.action_view_crm_email_add_cc_wizard)d"
icon="gtk-add" type="action"/>
</group>
<newline/>
<field name="description" colspan="4" nolabel="1"/>
<button colspan="4"
string="Reply to Last Email"
name="%(action_crm_send_mail)d"
context="{'mail':'reply', 'model': 'crm.helpdesk'}"
icon="gtk-undo" type="action" />
</form>
<tree string="Communication history">
<field name="description"/>
<field name="email_to"/>
<field name="date"/>
</tree>
</field>
<button colspan="4" string="Send New Email"
name="%(action_crm_send_mail)d"
context="{'mail':'new', 'model': 'crm.helpdesk'}"
icon="gtk-go-forward" type="action" />
</page>
</notebook>
</form>
</field>
</record>
<!-- Helpdesk Support Tree View -->
<record model="ir.ui.view" id="crm_case_tree_view_helpdesk">
<field name="name">CRM - Helpdesk Support Tree</field>
<field name="model">crm.helpdesk</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Helpdesk Support Tree"
colors="black:state=='open';blue:state=='pending';grey:state in ('cancel','close')">
<field name="id"/>
<field name="name" string="Query Description" />
<field name="partner_id" string="Partner"/>
<field name="date" string="Date"/>
<field name="date_deadline"/>
<field name="user_id"/>
<field name="section_id"/>
<field name="state"/>
<button name="case_close" string="Done"
states="open,draft,pending" type="object"
icon="gtk-close" />
<button name="case_open" string="Open"
states="draft,pending" type="object"
icon="gtk-go-forward" />
<button name="case_cancel" string="Cancel"
states="draft,open,pending" type="object"
icon="gtk-cancel" />
<button name="case_pending" string="Pending"
states="draft,open" type="object"
icon="gtk-media-pause" />
<button name="case_escalate" string="Escalate"
groups="base.group_extended"
states="open,draft,pending" type="object"
icon="gtk-go-up" />
<button name="case_reset" string="Reset to Draft"
states="done,cancel" type="object" icon="gtk-convert" />
</tree>
</field>
</record>
<!-- Helpdesk Support Calendar View -->
<record model="ir.ui.view" id="crm_case_helpdesk_calendar_view">
<field name="name">CRM - Helpdesk Support Calendar</field>
<field name="model">crm.helpdesk</field>
<field name="type">calendar</field>
<field name="priority" eval="2"/>
<field name="arch" type="xml">
<calendar string="Helpdesk Supports" date_start="date" color="user_id" date_delay="duration">
<field name="name"/>
<field name="partner_id"/>
<field name="categ_id"/>
</calendar>
</field>
</record>
<!-- Helpdesk Support Search View -->
<record id="view_crm_case_helpdesk_filter" model="ir.ui.view">
<field name="name">CRM - Helpdesk Search</field>
<field name="model">crm.helpdesk</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Search Helpdesk">
<filter icon="gtk-home" string="Today"
separator="1"
domain="[('date::date','=',time.strftime('%%Y-%%m-%%d'))]"
help="Todays's Helpdesk Requests"
/>
<filter icon="gtk-media-rewind"
string="7 Days" separator="1"
help="Helpdesk requests during last 7 days"
domain="[('date','&lt;', time.strftime('%%Y-%%m-%%d')), ('date','&gt;=',(datetime.date.today()-datetime.timedelta(days=7)).strftime('%%Y-%%m-%%d'))]"
/>
<separator orientation="vertical"/>
<field name="name" select='1' string="Query"/>
<field name="partner_id" />
<field name="user_id" select="1" widget="selection">
<filter icon="terp-partner"
domain="[('user_id','=',uid)]"
help="My Helpdesk Requests" default="1" />
</field>
<field name="section_id" select="1" widget="selection" string="Sales Team">
<filter icon="terp-crm"
domain="[('section_id','=',context.get('section_id',False))]"
help="My section" />
</field>
<newline/>
<group expand="1" string="Group By..." colspan="12" col="20">
<filter string="Customer" icon="terp-crm"
domain="[]" context="{'group_by':'partner_id'}" />
<filter string="State" icon="terp-crm" domain="[]"
context="{'group_by':'state'}" />
<filter string="Priority" icon="terp-crm"
domain="[]" context="{'group_by':'priority'}" />
</group>
</search>
</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from lxml import etree
from operator import itemgetter
from osv import fields, osv
import netsvc
import os
import pooler
import tools
class crm_installer(osv.osv_memory):
_name = 'crm.installer'
_inherit = 'res.config.installer'
_columns = {
'name': fields.char('Name', size=64),
'crm_helpdesk': fields.boolean('Helpdesk', help="Manages an Helpdesk service."),
'crm_fundraising': fields.boolean('Fundraising', help="This may help associations in their fund raising process and tracking."),
'crm_claim': fields.boolean('Claims', help="Manages the supplier and customers claims, including your corrective or preventive actions."),
'crm_caldav': fields.boolean('Calendar Synchronizing', help="Help you to synchronize the meetings with other calender clients(e.g.: Sunbird)."),
'sale_crm': fields.boolean('Create Quotation from Opportunity', help="This module relates sale to opportunity cases in the CRM."),
'fetchmail': fields.boolean('Fetch Emails', help="Fetchmail Server."),
'thunderbird': fields.boolean('Thunderbird', help="Thunderbird Interface."),
}
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
res = super(crm_installer, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False)
#Checking sale module is installed or not
cr.execute("SELECT * from ir_module_module where state='installed' and name = 'sale'")
count = cr.fetchall()
if count:
doc = etree.XML(res['arch'])
nodes = doc.xpath("//field[@name='sale_crm']")
for node in nodes:
node.set('invisible', '0')
res['arch'] = etree.tostring(doc)
return res
crm_installer()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,60 @@
<openerp>
<data>
<record id="view_crm_installer" model="ir.ui.view">
<field name="name">crm.installer.view</field>
<field name="model">crm.installer</field>
<field name="type">form</field>
<field name="inherit_id" ref="base.res_config_installer"/>
<field name="arch" type="xml">
<data>
<form position="attributes">
<attribute name="string">CRM Modules Installation</attribute>
</form>
<separator string="title" position="attributes">
<attribute name="string">Configure Your CRM System</attribute>
</separator>
<xpath expr="//label[@string='description']"
position="attributes">
<attribute name="string">You can enhance OpenERP's basic CRM support with a few additional OpenERP applications</attribute>
</xpath>
<group colspan="8">
<group colspan="2" col="2">
<separator string="Synchronization" colspan="4" />
<field name="crm_caldav" />
<field name="fetchmail" />
</group>
<group colspan="2" col="2">
<separator string="Plugins" colspan="4" />
<field name="thunderbird" />
<label string="*Outlook*" />
<!-- TODO <field name="outlook" />-->
</group>
<group colspan="4" col="4" groups="base.group_extended" name="other">
<separator string="Additional features" colspan="4" />
<field name="crm_claim" />
<field name="crm_helpdesk" />
<field name="crm_fundraising" />
<field name="sale_crm" invisible="1"/>
</group>
</group>
</data>
</field>
</record>
<record id="action_crm_installer" model="ir.actions.act_window">
<field name="name">CRM Modules Installation</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">crm.installer</field>
<field name="view_id" ref="view_crm_installer"/>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record id="crm_installer_todo" model="ir.actions.todo">
<field name="action_id" ref="action_crm_installer"/>
<field name="sequence">3</field>
</record>
</data>
</openerp>

View File

@ -26,27 +26,15 @@ import math
import time
import mx.DateTime
from tools.translate import _
from crm import crm_case
class crm_lead(osv.osv):
class crm_lead(osv.osv, crm_case):
""" CRM Lead Case """
_name = "crm.lead"
_description = "Leads Cases"
_order = "priority, id desc"
_inherit = ['res.partner.address', 'crm.case']
def case_open(self, cr, uid, ids, *args):
"""
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case's Ids
@param *args: Give Tuple Value
"""
res = super(crm_lead, self).case_open(cr, uid, ids, *args)
self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
return res
_inherit = ['res.partner.address', 'mailgate.thread']
def _compute_day(self, cr, uid, ids, fields, args, context={}):
"""
@ -85,8 +73,8 @@ class crm_lead(osv.osv):
resource_id = resource_ids[0]
duration = float(ans.days)
if lead.section_id.resource_calendar_id:
duration = float(ans.days) * 24
if lead.section_id and lead.section_id.resource_calendar_id:
duration = float(ans.days) * 24
new_dates = cal_obj.interval_get(cr,
uid,
lead.section_id.resource_calendar_id and lead.section_id.resource_calendar_id.id or False,
@ -106,22 +94,36 @@ class crm_lead(osv.osv):
return res
_columns = {
# From crm.case
'email_from': fields.char('Email', size=128, help="These people will receive email."),
'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.'),
'create_date': fields.datetime('Creation Date' , readonly=True),
'email_cc': fields.text('Watchers Emails', size=252 , help="These \
people will receive a copy of the future communication between partner \
and users by email"),
'description': fields.text('Description'),
'write_date': fields.datetime('Update Date' , readonly=True),
# Lead fields
'categ_id': fields.many2one('crm.case.categ', 'Lead Source', \
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.opportunity')]"),
('object_id.model', '=', 'crm.lead')]"),
'type_id': fields.many2one('crm.case.resource.type', 'Lead Type', \
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.lead')]"),
'partner_name': fields.char("Company Name", size=64),
'partner_name': fields.char("Partner Name", size=64),
'type':fields.selection([
('lead','Lead'),
('opportunity','Opportunity'),
],'Type', help="Type is used to separate Leads and Opportunities"),
'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
'date_closed': fields.datetime('Closed', readonly=True),
'stage_id': fields.many2one('crm.case.stage', 'Stage', \
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.lead')]"),
'opportunity_id': fields.many2one('crm.opportunity', 'Opportunity'),
'user_id': fields.many2one('res.users', 'Salesman'),
'referred': fields.char('Referred By', size=64),
'date_open': fields.datetime('Opened', readonly=True),
@ -129,14 +131,47 @@ class crm_lead(osv.osv):
method=True, multi='day_open', type="float", store=True),
'day_close': fields.function(_compute_day, string='Days to Close', \
method=True, multi='day_close', type="float", store=True),
'function_name': fields.char('Function', size=64),
'function_name': fields.char('Function', size=64),
'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
help='The state is set to \'Draft\', when a case is created.\
\nIf the case is in progress the state is set to \'Open\'.\
\nWhen the case is over, the state is set to \'Done\'.\
\nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
}
_defaults = {
'active': lambda *a: 1,
'user_id': crm_case._get_default_user,
'email_from': crm_case._get_default_email,
'state': lambda *a: 'draft',
'section_id': crm_case._get_section,
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
}
def case_open(self, cr, uid, ids, *args):
"""Overrides cancel for crm_case for setting Open Date
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case's Ids
@param *args: Give Tuple Value
"""
res = super(crm_lead, self).case_open(cr, uid, ids, *args)
self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
return res
def case_close(self, cr, uid, ids, *args):
"""Overrides close for crm_case for setting close date
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
res = super(crm_lead, self).case_close(cr, uid, ids, args)
self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
return res
def convert_opportunity(self, cr, uid, ids, context=None):
""" Precomputation for converting lead to opportunity
@ -158,7 +193,6 @@ class crm_lead(osv.osv):
if data_id:
view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
for case in self.browse(cr, uid, ids):
context.update({'opportunity_id': case.id})
context.update({'active_id': case.id})
if not case.partner_id:
data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')

View File

@ -9,72 +9,77 @@
<record id="crm_case_itisatelesalescampaign0" model="crm.lead">
<field name="type_id" ref="crm.type_lead1"/>
<field eval="'3'" name="priority"/>
<field name="type">lead</field>
<field name="user_id" ref="base.user_root"/>
<field eval="'Luc Latour'" name="name"/>
<field eval="'Luc Latour'" name="partner_name"/>
<field eval="'open'" name="state"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field eval="'(726) 782-0636'" name="mobile"/>
<field eval="1" name="active"/>
<field name="categ_id" ref="crm.categ_oppor6"/>
<field name="stage_id" ref="crm.stage_lead1"/>
<field eval="'The Oil Company'" name="partner_name"/>
<field eval="'The Oil Company'" name="name"/>
<field eval="'(769) 703-274'" name="phone"/>
</record>
<record id="crm_case_electonicgoodsdealer0" model="crm.lead">
<field name="type_id" ref="crm.type_lead7"/>
<field eval="'4'" name="priority"/>
<field name="type">lead</field>
<field name="user_id" ref="base.user_root"/>
<field eval="'Marc Dufour'" name="name"/>
<field eval="'Marc Dufour'" name="partner_name"/>
<field eval="'open'" name="state"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field eval="'(392) 895-7917'" name="mobile"/>
<field eval="1" name="active"/>
<field name="categ_id" ref="crm.categ_oppor2"/>
<field name="stage_id" ref="crm.stage_lead2"/>
<field eval="'Le Club SARL'" name="partner_name"/>
<field eval="'Le Club SARL'" name="name"/>
<field eval="'(956) 293-2595'" name="phone"/>
</record>
<record id="crm_case_developingwebapplications0" model="crm.lead">
<field name="type_id" ref="crm.type_lead5"/>
<field eval="'2'" name="priority"/>
<field name="type">lead</field>
<field name="user_id" ref="base.user_demo"/>
<field eval="'Fabien Pinckaers'" name="name"/>
<field eval="'Fabien Pinckaers'" name="partner_name"/>
<field eval="'done'" name="state"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field eval="'(820) 167-3208'" name="mobile"/>
<field eval="1" name="active"/>
<field name="categ_id" ref="crm.categ_oppor4"/>
<field name="stage_id" ref="crm.stage_lead4"/>
<field eval="'TheKompany'" name="partner_name"/>
<field eval="'TheKompany'" name="name"/>
<field eval="'(079) 681-2139'" name="phone"/>
<field eval="'contact@tecsas.fr'" name="email_from"/>
</record>
<record id="crm_case_qrecorp0" model="crm.lead">
<field name="type_id" ref="crm.type_lead8"/>
<field eval="'3'" name="priority"/>
<field name="type">lead</field>
<field name="user_id" ref="base.user_root"/>
<field eval="'Henry Mc Coy'" name="name"/>
<field eval="'Henry Mc Coy'" name="partner_name"/>
<field eval="'draft'" name="state"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field eval="'(077) 582-4035'" name="mobile"/>
<field eval="1" name="active"/>
<field name="categ_id" ref="crm.categ_oppor1"/>
<field name="stage_id" ref="crm.stage_lead1"/>
<field eval="'The Gas Company'" name="partner_name"/>
<field eval="'The Gas Company'" name="name"/>
<field eval="'(514) 698-4118'" name="phone"/>
</record>
<record id="crm_case_itdeveloper0" model="crm.lead">
<field name="type_id" ref="crm.type_lead4"/>
<field eval="'3'" name="priority"/>
<field name="type">lead</field>
<field name="user_id" ref="base.user_root"/>
<field eval="'Carrie Helle'" name="name"/>
<field eval="'Carrie Helle'" name="partner_name"/>
<field eval="'pending'" name="state"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field eval="'(333) 715-1450'" name="mobile"/>
<field eval="1" name="active"/>
<field name="categ_id" ref="crm.categ_oppor6"/>
<field name="stage_id" ref="crm.stage_lead3"/>
<field eval="'Stonage IT'" name="partner_name"/>
<field eval="'Stonage IT'" name="name"/>
<field eval="'(855) 924-4364'" name="phone"/>
</record>
<record id="crm_case_mgroperations0" model="crm.lead">
@ -83,14 +88,15 @@
<field name="type_id" ref="crm.type_lead3"/>
<field name="partner_id" ref="base.res_partner_9"/>
<field eval="'3'" name="priority"/>
<field name="type">lead</field>
<field name="user_id" ref="base.user_root"/>
<field eval="'Tina Pinero'" name="name"/>
<field eval="'Tina Pinero'" name="partner_name"/>
<field eval="'cancel'" name="state"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field eval="'(468) 017-2684'" name="mobile"/>
<field name="categ_id" ref="crm.categ_oppor8"/>
<field name="stage_id" ref="crm.stage_lead6"/>
<field eval="'Opensides SPRL'" name="partner_name"/>
<field eval="'Opensides SPRL'" name="name"/>
<field eval="'(373) 907-1009'" name="phone"/>
<field eval="'info@opensides.be'" name="email_from"/>
</record>
@ -98,14 +104,15 @@
<field eval="1" name="active"/>
<field name="type_id" ref="crm.type_lead8"/>
<field eval="'3'" name="priority"/>
<field name="type">lead</field>
<field name="user_id" ref="base.user_root"/>
<field eval="'Wendi Baltz'" name="name"/>
<field eval="'Wendi Baltz'" name="partner_name"/>
<field eval="'done'" name="state"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field eval="'(463) 014-1208'" name="mobile"/>
<field name="categ_id" ref="crm.categ_oppor4"/>
<field name="stage_id" ref="crm.stage_lead4"/>
<field eval="'Gardner Group'" name="partner_name"/>
<field eval="'Gardner Group'" name="name"/>
<field eval="'(282) 603-7489'" name="phone"/>
</record>
</data>

View File

@ -6,15 +6,19 @@
<field name="name">Leads</field>
<field name="res_model">crm.lead</field>
<field name="view_mode">tree,form</field>
<field name="domain">['|', ('type','=','lead'), ('type','=',False)]</field>
<field name="view_id" ref="crm_case_tree_view_leads"/>
<field name="search_view_id" ref="crm.view_crm_case_leads_filter"/>
<field name="context">{'search_default_current':1}</field>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_tree_view_leads_all">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="crm_case_tree_view_leads"/>
<field name="act_window_id" ref="crm_case_category_act_leads_all"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_form_view_leads_all">
<field name="sequence" eval="3"/>
<field name="view_mode">form</field>
@ -24,6 +28,8 @@
<menuitem id="base.menu_sales" name="Sales"
parent="base.menu_base_partner" sequence="1" />
<menuitem parent="base.menu_sales" name="Leads"
groups="base.group_extended"
id="menu_crm_case_categ0_act_leads"

View File

@ -40,7 +40,7 @@
<field name="arch" type="xml">
<form string="Leads Form">
<group colspan="4" col="7">
<field name="name" required="1"/>
<field name="name" required="1" string="Name"/>
<field name="priority"/>
<field name="date_deadline"/>
<button
@ -48,8 +48,7 @@
string="Convert"
help="Convert to Opportunity"
icon="gtk-index"
type="object"
attrs="{'invisible':[('opportunity_id','!=',False)]}"/>
type="object"/>
<newline />
<field name="section_id" colspan="1"
widget="selection" />
@ -69,7 +68,7 @@
<page string="Lead">
<group colspan="2" col="4">
<separator string="Contact" colspan="4" col="4"/>
<field name="partner_name" colspan="4"/>
<field name="partner_name" string="Partner Name" colspan="4"/>
<newline/>
<field domain="[('domain', '=', 'contact')]" name="title"/>
<field name="function_name" />
@ -97,7 +96,6 @@
icon="gtk-index" type="action"
string="Create"
attrs="{'invisible':[('partner_id','!=',False)]}"/>
<field name="opportunity_id"/>
</group>
<separator colspan="4" string="Notes"/>
<field name="description" nolabel="1" colspan="4"/>
@ -133,7 +131,7 @@
widget="selection" colspan="2" />
<field name="categ_id"
widget="selection"
domain="[('object_id.model', '=', 'crm.opportunity')]" />
domain="[('object_id.model', '=', 'crm.lead')]" />
<field name="type_id" string="Campaign" select="1"/>
<field name="referred"/>
</group>
@ -154,16 +152,13 @@
<group colspan="4">
<field colspan="4" name="email_cc" string="CC"/>
</group>
<field name="history_line" colspan="4" nolabel="1" mode="form,tree">
<!-- TODO-->
<field name="message_ids" colspan="4" nolabel="1" mode="form,tree">
<form string="Communication history">
<group col="7" colspan="4">
<group col="6" colspan="4">
<field name="date"/>
<field name="email_to"/>
<field name="email_from"/>
<button
string="Add a CC"
name="%(crm.action_view_crm_email_add_cc_wizard)d"
icon="gtk-add" type="action"/>
</group>
<newline/>
<field name="description" colspan="4" nolabel="1"/>
@ -180,7 +175,7 @@
<field name="email_to"/>
<field name="description"/>
</tree>
</field>
</field>
<button colspan="2" string="Send New Email"
name="%(action_crm_send_mail)d"
context="{'mail':'new', 'model': 'crm.lead'}"
@ -271,7 +266,7 @@
<search string="Search Leads">
<filter icon="terp-project"
string="Current"
default="1"
default="1" name="current"
domain="[('state','in',('draft','open'))]"/>
<filter icon="terp-project"
string="Open"
@ -314,7 +309,7 @@
help="Show Sales Team"/>
</field>
<newline/>
<group expand="1" string="Group By..." colspan="14">
<group expand="0" string="Group By..." colspan="14">
<filter string="Stage" icon="terp-crm" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="State" icon="terp-crm" domain="[]" context="{'group_by':'state'}"/>
<filter string="Source" icon="terp-crm" domain="[]" context="{'group_by':'categ_id'}"/>

View File

@ -38,8 +38,8 @@ from tools import command_re
class crm_cases(osv.osv):
""" crm cases """
_name = "crm.case"
_inherit = "crm.case"
_name = "mailgate.thread"
_inherit = "mailgate.thread"
def message_new(self, cr, uid, msg, context):
"""

View File

@ -19,38 +19,50 @@
#
##############################################################################
from osv import fields, osv
import crm
from datetime import datetime, timedelta
from datetime import datetime, timedelta
from tools.translate import _
from base_calendar import base_calendar
from crm import crm_case
from datetime import datetime, timedelta
from osv import fields, osv
from tools.translate import _
import time
class crm_opportunity(osv.osv):
_name = 'crm.opportunity'
crm_opportunity()
class crm_lead(osv.osv, crm_case):
""" CRM Leads """
_name = 'crm.lead'
crm_lead()
class crm_phonecall(osv.osv):
class crm_phonecall(osv.osv, crm_case):
""" CRM Phonecall """
_name = 'crm.phonecall'
crm_phonecall()
class crm_meeting(osv.osv):
class crm_meeting(osv.osv, crm_case):
""" CRM Meeting Cases """
_name = 'crm.meeting'
_description = "Meeting Cases"
_order = "id desc"
_inherit = ["crm.case", "calendar.event"]
_inherit = ["mailgate.thread", "calendar.event"]
_columns = {
# From crm.case
'name': fields.char('Summary', size=124, required=True),
'partner_id': fields.many2one('res.partner', 'Partner'),
'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \
domain="[('partner_id','=',partner_id)]"),
'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.'),
'email_from': fields.char('Email', size=128, help="These people will receive email."),
'id': fields.integer('ID'),
# Meeting fields
'categ_id': fields.many2one('crm.case.categ', 'Meeting Type', \
domain="[('object_id.model', '=', 'crm.meeting')]", \
),
'phonecall_id': fields.many2one ('crm.phonecall', 'Phonecall'),
'opportunity_id': fields.many2one ('crm.opportunity', 'Opportunity'),
'opportunity_id': fields.many2one ('crm.lead', 'Opportunity', domain="[('type', '=', 'opportunity')]"),
'attendee_ids': fields.many2many('calendar.attendee', 'meeting_attendee_rel',\
'event_id', 'attendee_id', 'Attendees'),
'date_closed': fields.datetime('Closed', readonly=True),
@ -63,9 +75,10 @@ class crm_meeting(osv.osv):
}
_defaults = {
'state': lambda *a: 'draft',
'state': lambda *a: 'draft',
'active': lambda *a: 1,
}
def open_meeting(self, cr, uid, ids, context=None):
"""
Open Crm Meeting Form for Crm Meeting.
@ -75,13 +88,14 @@ class crm_meeting(osv.osv):
@param context: A standard dictionary for contextual values
@return: Dictionary value which open Crm Meeting form.
"""
if not context:
context = {}
data_obj = self.pool.get('ir.model.data')
value = {}
id2 = data_obj._get_id(cr, uid, 'crm', 'crm_case_form_view_meet')
id3 = data_obj._get_id(cr, uid, 'crm', 'crm_case_tree_view_meet')
id4 = data_obj._get_id(cr, uid, 'crm', 'crm_case_calendar_view_meet')
@ -93,19 +107,19 @@ class crm_meeting(osv.osv):
id4 = data_obj.browse(cr, uid, id4, context=context).res_id
for id in ids:
value = {
'name': _('Meeting'),
'view_type': 'form',
'view_mode': 'form,tree',
'res_model': 'crm.meeting',
'view_id': False,
'name': _('Meeting'),
'view_type': 'form',
'view_mode': 'form,tree',
'res_model': 'crm.meeting',
'view_id': False,
'views': [(id2, 'form'), (id3, 'tree'), (id4, 'calendar')],
'type': 'ir.actions.act_window',
'res_id': base_calendar.base_calendar_id2real_id(id),
'type': 'ir.actions.act_window',
'res_id': base_calendar.base_calendar_id2real_id(id),
'nodestroy': True
}
return value
return value
crm_meeting()
class calendar_attendee(osv.osv):
@ -143,4 +157,16 @@ class calendar_attendee(osv.osv):
calendar_attendee()
class res_partner(osv.osv):
""" Inherits partner and adds meetings information in the partner form """
_inherit = 'res.partner'
_columns = {
'meeting_ids': fields.one2many('crm.meeting', 'partner_id',\
'Meetings'),
}
res_partner()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -17,7 +17,7 @@
<field eval="time.strftime('%Y-%m-03 10:20:03')" name="date"/>
<field name="categ_id" ref="crm.categ_meet2"/>
<field eval="&quot;Follow-up on proposal&quot;" name="name"/>
<field eval="time.strftime('%Y-%m-10')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-03 16:38:03')" name="date_deadline"/>
<field eval="6.3" name="duration"/>
</record>
@ -32,7 +32,7 @@
<field eval="time.strftime('%Y-%m-05 12:01:01')" name="date"/>
<field name="categ_id" ref="crm.categ_meet3"/>
<field eval="&quot;Initial discussion&quot;" name="name"/>
<field eval="time.strftime('%Y-%m-12')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-05 19:01:01')" name="date_deadline"/>
<field eval="&quot;contact@tecsas.fr&quot;" name="email_from"/>
</record>
@ -48,9 +48,9 @@
<field eval="time.strftime('%Y-%m-12 15:55:05')" name="date"/>
<field name="categ_id" ref="crm.categ_meet1"/>
<field eval="&quot;Discuss pricing&quot;" name="name"/>
<field eval="time.strftime('%Y-%m-20')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-12 18:55:05')" name="date_deadline"/>
</record>
<record id="crm_case_reviewneeds0" model="crm.meeting">
<field name="partner_address_id" ref="base.res_partner_address_15"/>
<field eval="1" name="active"/>
@ -62,9 +62,9 @@
<field eval="time.strftime('%Y-%m-20 10:02:02')" name="date"/>
<field name="categ_id" ref="crm.categ_meet3"/>
<field eval="&quot;Review needs&quot;" name="name"/>
<field eval="time.strftime('%Y-%m-25')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-20 16:02:02')" name="date_deadline"/>
</record>
<record id="crm_case_changesindesigning0" model="crm.meeting">
<field name="partner_address_id" ref="base.res_partner_address_1"/>
<field eval="1" name="active"/>
@ -76,7 +76,7 @@
<field eval="time.strftime('%Y-%m-22 11:05:05')" name="date"/>
<field name="categ_id" ref="crm.categ_meet2"/>
<field eval="&quot;Changes in Designing&quot;" name="name"/>
<field eval="time.strftime('%Y-%m-28')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-22 16:05:05')" name="date_deadline"/>
<field eval="&quot;info@opensides.be&quot;" name="email_from"/>
</record>
@ -91,7 +91,7 @@
<field name="categ_id" ref="crm.categ_meet2"/>
<field eval="&quot;Update the data&quot;" name="name"/>
<field eval="13.3" name="duration"/>
<field eval="time.strftime('%Y-%m-28')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-19 02:30:49')" name="date_deadline"/>
</record>
</data>
</openerp>

View File

@ -12,7 +12,6 @@
domain="[('partner_id', '=', active_id)]"
/>
<record model="ir.actions.act_window" id="crm_case_categ_meet_create_partner">
<field name="context" eval="{'default_state':'draft'}"/>
</record>
@ -24,18 +23,21 @@
<field name="view_id" ref="crm_case_calendar_view_meet"/>
<field name="act_window_id" ref="crm_case_categ_meet_create_partner"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_tree_meet_partner">
<field name="sequence" eval="2"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="crm_case_tree_view_meet"/>
<field name="act_window_id" ref="crm_case_categ_meet_create_partner"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_form_meet_partner">
<field name="sequence" eval="3"/>
<field name="view_mode">form</field>
<field name="view_id" ref="crm_case_form_view_meet"/>
<field name="act_window_id" ref="crm_case_categ_meet_create_partner"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_gantt_meet_partner">
<field name="sequence" eval="3"/>
<field name="view_mode">gantt</field>
@ -43,68 +45,36 @@
<field name="act_window_id" ref="crm_case_categ_meet_create_partner"/>
</record>
<act_window
id="crm_case_categ_meet_case"
name="Schedule a Meeting"
res_model="crm.meeting"
src_model="crm.case"
view_mode="calendar,tree,form,gantt"
context="{'default_case_id': active_id, 'default_duration': 4.0}"
/>
<record model="ir.actions.act_window" id="crm_case_categ_meet_case">
<field name="context" eval="{'default_state':'draft'}"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_calendar_meet_case">
<field name="sequence" eval="1"/>
<field name="view_mode">calendar</field>
<field name="view_id" ref="crm_case_calendar_view_meet"/>
<field name="act_window_id" ref="crm_case_categ_meet_case"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_tree_meet_case">
<field name="sequence" eval="2"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="crm_case_tree_view_meet"/>
<field name="act_window_id" ref="crm_case_categ_meet_case"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_form_meet_case">
<field name="sequence" eval="3"/>
<field name="view_mode">form</field>
<field name="view_id" ref="crm_case_form_view_meet"/>
<field name="act_window_id" ref="crm_case_categ_meet_case"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_gantt_meet_case">
<field name="sequence" eval="3"/>
<field name="view_mode">gantt</field>
<field name="view_id" ref="crm_case_gantt_view_meet"/>
<field name="act_window_id" ref="crm_case_categ_meet_case"/>
</record>
<record model="ir.actions.act_window" id="crm_case_categ_meet">
<field name="name">Meetings</field>
<field name="res_model">crm.meeting</field>
<field name="view_mode">calendar,tree,form,gantt</field>
<field name="view_id" ref="crm_case_calendar_view_meet"/>
<field name="context">{'search_default_current':1}</field>
<field name="search_view_id" ref="view_crm_case_meetings_filter"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_calendar_meet">
<field name="sequence" eval="1"/>
<field name="view_mode">calendar</field>
<field name="view_id" ref="crm_case_calendar_view_meet"/>
<field name="act_window_id" ref="crm_case_categ_meet"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_tree_meet">
<field name="sequence" eval="2"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="crm_case_tree_view_meet"/>
<field name="act_window_id" ref="crm_case_categ_meet"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_form__meet">
<field name="sequence" eval="3"/>
<field name="view_mode">form</field>
<field name="view_id" ref="crm_case_form_view_meet"/>
<field name="act_window_id" ref="crm_case_categ_meet"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_gantt_meet">
<field name="sequence" eval="1"/>
<field name="view_mode">gantt</field>
@ -142,43 +112,5 @@
name="Meeting Invitations" parent="crm.menu_meeting_sale"
sequence="10" action="action_view_attendee_form" />
<record model="ir.actions.act_window" id="crm_case_category_act_meetall3">
<field name="name">Next Meetings</field>
<field name="res_model">crm.meeting</field>
<field name="view_type">form</field>
<field name="view_mode">tree,calendar,form,gantt</field>
<field name="view_id" ref="crm_case_calendar_view_meet"/>
<field name="domain"
eval="[('state','in',('pending','draft','open')),('date','>=',time.strftime('%Y-%m-%d'))]" />
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_calendar_view_meetall3">
<field name="sequence" eval="2"/>
<field name="view_mode">calendar</field>
<field name="view_id" ref="crm_case_calendar_view_meet"/>
<field name="act_window_id" ref="crm_case_category_act_meetall3"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_tree_view_meetall3">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="crm_case_tree_view_meet"/>
<field name="act_window_id" ref="crm_case_category_act_meetall3"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_form_view_meetall3">
<field name="sequence" eval="3"/>
<field name="view_mode">form</field>
<field name="view_id" ref="crm_case_form_view_meet"/>
<field name="act_window_id" ref="crm_case_category_act_meetall3"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_gantt_view_meetall3">
<field name="sequence" eval="2"/>
<field name="view_mode">gantt</field>
<field name="view_id" ref="crm_case_gantt_view_meet"/>
<field name="act_window_id" ref="crm_case_category_act_meetall3"/>
</record>
</data>
</openerp>

View File

@ -24,15 +24,15 @@
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Meetings">
<group col="6" colspan="4">
<group col="6" colspan="4">
<field name="name" select="1" string="Summary"
colspan="4" />
colspan="2" />
<field name="categ_id" widget="selection"
string="Meeting Type"
groups="base.group_extended"
domain="[('object_id.model', '=', 'crm.meeting')]" />
<newline/>
<field name="date" string="Start Date" required="1"
<field name="allday" colspan="2" on_change="onchange_allday(allday)" />
<field name="date" string="Start Date" required="1"
on_change="onchange_dates(date,duration,False)" />
<field name="duration" widget="float_time"
on_change="onchange_dates(date,duration,False)" />
@ -53,7 +53,7 @@
</group>
</group>
<group col="4" colspan="4" name="rrule" attrs="{'invisible': [('rrule_type','!=','custom')]}">
<separator string="Select data for Custom Rule" colspan="8"/>
<separator string="Data for Custom Rule" colspan="8"/>
<group col="8" colspan="4">
<field name="freq" />
<field name="interval" />
@ -116,7 +116,7 @@
<separator colspan="2" string="Visibility"/>
<field name="class" string="Privacy"/>
<field name="show_as" string="Show time as" groups="base.group_extended"/>
<field name="rrule" groups="base.group_extended" />
<field name="rrule" invisible="1" />
<field name="recurrent_id" invisible="1"/>
<field name="recurrent_uid" invisible="1"/>
</group>
@ -129,6 +129,9 @@
<button name="case_close" string="Done"
states="open" type="object"
icon="gtk-jump-to" />
<button name="case_reset" string="Reset to Unconfirmed"
states="open,done,cancel" type="object"
icon="gtk-convert" />
<button name="case_open" string="Confirm"
states="draft" type="object"
icon="gtk-go-forward" />
@ -211,7 +214,7 @@
<field name="date" string="Meeting Date" />
<field name="duration" />
<field name="user_id" />
<field name="state" invisible="1"/>
<field name="state"/>
</tree>
</field>
</record>
@ -257,12 +260,12 @@
<field name="arch" type="xml">
<search string="Search Meetings">
<group col="12" colspan="4">
<filter icon="terp-crm" string="My Meetings"
<filter icon="terp-project" name="current" string="Current"
domain="[('state','in',('draft', 'open'))]"
help="Current Meetings"/>
<filter icon="terp-partner" string="My Meetings"
domain="[('user_id','=',uid)]"
help="My Meetings" />
<filter icon="terp-crm" name="current" string="Current"
domain="[('state','in',('draft', 'open'))]"
help="Current Meetings"/>
<separator orientation="vertical"/>
<field name="name" select="1" string="Subject"/>
<field name="partner_id" select="1" />
@ -274,6 +277,22 @@
</field>
<field name="user_id" select="1" widget="selection"/>
</group>
<newline/>
<group expand="0" string="Group By..." colspan="16">
<filter string="Date" icon="terp-project"
domain="[]" context="{'group_by':'date'}" />
<filter string="Privacy" icon="terp-crm"
domain="[]" context="{'group_by':'class'}" />
<separator orientation="vertical" />
<filter string="Responsible" icon="terp-crm" domain="[]"
context="{'group_by':'user_id'}" />
<filter string="Partner" icon="terp-partner" domain="[]"
context="{'group_by':'partner_id'}" />
<separator orientation="vertical" />
<filter string="State" icon="terp-crm" domain="[]"
context="{'group_by':'state'}" />
<separator orientation="vertical" />
</group>
</search>
</field>
</record>
@ -305,5 +324,22 @@
</field>
</field>
</record>
<!-- Partners inherited form -->
<record id="view_meeting_partner_info_form" model="ir.ui.view">
<field name="name">res.partner.meeting.info.inherit</field>
<field name="model">res.partner</field>
<field name="type">form</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<notebook position="inside">
<page string="CRM Info">
<field name="meeting_ids" colspan="4" nolabel="1" />
</page>
</notebook>
</field>
</record>
</data>
</openerp>

View File

@ -37,112 +37,62 @@ AVAILABLE_STATES = [
class crm_opportunity(osv.osv):
""" Opportunity Cases """
_name = "crm.opportunity"
_name = "crm.lead"
_description = "Opportunity Cases"
_order = "priority,date_action,id desc"
_inherit = 'crm.case'
_inherit = 'crm.lead'
def case_open(self, cr, uid, ids, *args):
_columns = {
# From crm.case
'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \
domain="[('partner_id','=',partner_id)]"),
# Opportunity fields
'probability': fields.float('Probability (%)'),
'planned_revenue': fields.float('Expected Revenue'),
'ref': fields.reference('Reference', selection=crm._links_get, size=128),
'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128),
'phone': fields.char("Phone", size=64),
'date_deadline': fields.date('Expected Closing'),
'date_action': fields.date('Next Action'),
}
def case_close(self, cr, uid, ids, *args):
"""Overrides close for crm_case for setting probability and close date
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
res = super(crm_opportunity, self).case_close(cr, uid, ids, args)
self.write(cr, uid, ids, {'probability' : 100.0, 'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
return res
def case_cancel(self, cr, uid, ids, *args):
"""Overrides cancel for crm_case for setting probability
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
res = super(crm_opportunity, self).case_cancel(cr, uid, ids, args)
self.write(cr, uid, ids, {'probability' : 0.0})
return res
def case_open(self, cr, uid, ids, *args):
"""Overrides cancel for crm_case for setting Open Date
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case's Ids
@param *args: Give Tuple Value
"""
res = super(crm_opportunity, self).case_open(cr, uid, ids, *args)
self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
return res
def _compute_day(self, cr, uid, ids, fields, args, context={}):
"""
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of Opendays IDs
@return: difference between current date and log date
@param context: A standard dictionary for contextual values
"""
cal_obj = self.pool.get('resource.calendar')
res_obj = self.pool.get('resource.resource')
res = {}
for opportunity in self.browse(cr, uid, ids , context):
for field in fields:
res[opportunity.id] = {}
duration = 0
ans = False
if field == 'day_open':
if opportunity.date_open:
date_create = datetime.strptime(opportunity.create_date, "%Y-%m-%d %H:%M:%S")
date_open = datetime.strptime(opportunity.date_open, "%Y-%m-%d %H:%M:%S")
ans = date_open - date_create
date_until = opportunity.date_open
elif field == 'day_close':
if opportunity.date_closed:
date_create = datetime.strptime(opportunity.create_date, "%Y-%m-%d %H:%M:%S")
date_close = datetime.strptime(opportunity.date_closed, "%Y-%m-%d %H:%M:%S")
date_until = opportunity.date_closed
ans = date_close - date_create
if ans:
resource_id = False
if opportunity.user_id:
resource_ids = res_obj.search(cr, uid, [('user_id','=',opportunity.user_id.id)])
if resource_ids and len(resource_ids):
resource_id = resource_ids[0]
duration = float(ans.days)
if opportunity.section_id.resource_calendar_id:
duration = float(ans.days) * 24
new_dates = cal_obj.interval_get(cr,
uid,
opportunity.section_id.resource_calendar_id and opportunity.section_id.resource_calendar_id.id or False,
mx.DateTime.strptime(opportunity.create_date, '%Y-%m-%d %H:%M:%S'),
duration,
resource=resource_id
)
no_days = []
date_until = mx.DateTime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
for in_time, out_time in new_dates:
if in_time.date not in no_days:
no_days.append(in_time.date)
if out_time > date_until:
break
duration = len(no_days)
res[opportunity.id][field] = abs(int(duration))
return res
_columns = {
'stage_id': fields.many2one ('crm.case.stage', 'Stage', \
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.opportunity')]"),
'categ_id': fields.many2one('crm.case.categ', 'Category', \
domain="[('section_id','=',section_id), \
('object_id.model', '=', 'crm.opportunity')]"),
'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
'referred': fields.char('Referred By', size=64),
'probability': fields.float('Probability (%)'),
'planned_revenue': fields.float('Expected Revenue'),
'ref': fields.reference('Reference', selection=crm._links_get, size=128),
'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128),
'date_closed': fields.datetime('Closed', readonly=True),
'user_id': fields.many2one('res.users', 'Salesman'),
'phone': fields.char("Phone", size=64),
'date_deadline': fields.date('Expected Closing'),
'date_action': fields.date('Next Action'),
'state': fields.selection(AVAILABLE_STATES, 'State', size=16, readonly=True,
help='The state is set to \'Draft\', when a case is created.\
\nIf the case is in progress the state is set to \'Open\'.\
\nWhen the case is over, the state is set to \'Done\'.\
\nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
'date_open': fields.datetime('Opened', readonly=True),
'day_open': fields.function(_compute_day, string='Days to Open', \
method=True, multi='day_open', type="float", store=True),
'day_close': fields.function(_compute_day, string='Days to Close', \
method=True, multi='day_close', type="float", store=True),
}
def onchange_stage_id(self, cr, uid, ids, stage_id, context={}):
""" @param self: The object pointer
@ -157,40 +107,11 @@ class crm_opportunity(osv.osv):
stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context)
if not stage.on_change:
return {'value':{}}
return {'value':{'probability':stage.probability}}
def stage_next(self, cr, uid, ids, context={}):
""" @param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of stage nexts IDs
@param context: A standard dictionary for contextual values """
res = super(crm_opportunity, self).stage_next(cr, uid, ids, context=context)
for case in self.browse(cr, uid, ids, context):
if case.stage_id and case.stage_id.on_change:
self.write(cr, uid, [case.id], {'probability': case.stage_id.probability})
return res
def stage_previous(self, cr, uid, ids, context={}):
""" @param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of stage previouss IDs
@param context: A standard dictionary for contextual values """
res = super(crm_opportunity, self).stage_previous(cr, uid, ids, context=context)
for case in self.browse(cr, uid, ids, context):
if case.stage_id and case.stage_id.on_change:
self.write(cr, uid, [case.id], {'probability': case.stage_id.probability})
return res
return {'value':{'probability': stage.probability}}
_defaults = {
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.opportunity', context=c),
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
'state' : 'draft',
}
def action_makeMeeting(self, cr, uid, ids, context=None):
@ -231,7 +152,6 @@ class crm_opportunity(osv.osv):
}
value = {
'name': _('Meetings'),
'domain': "[('user_id','=',%s)]" % (uid),
'context': context,
'view_type': 'form',
'view_mode': 'calendar,form,tree',
@ -242,8 +162,20 @@ class crm_opportunity(osv.osv):
'search_view_id': res['res_id'],
'nodestroy': True
}
return value
crm_opportunity()
class res_partner(osv.osv):
""" Inherits partner and adds Opportunities information in the partner form """
_inherit = 'res.partner'
_columns = {
'opportunity_ids': fields.one2many('crm.lead', 'partner_id',\
'Opportunities', domain=[('type', '=', 'opportunity')]),
}
res_partner()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -4,91 +4,91 @@
<record model="crm.case.categ" id="categ_oppor1">
<field name="name">Existing Customer</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor2">
<field name="name">Self Generated</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor3">
<field name="name">Employee</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor4">
<field name="name">Partner</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor5">
<field name="name">Campaign</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor6">
<field name="name">Website</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor7">
<field name="name">Word of mouth</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor8">
<field name="name">Other</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<!-- CASE STATUS(stage_id) -->
<record model="crm.case.stage" id="stage_oppor1">
<field name="name">Prospecting</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.stage" id="stage_oppor2">
<field name="name">Needs Analysis</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.stage" id="stage_oppor3">
<field name="name">Value Proposition</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.stage" id="stage_oppor4">
<field name="name">Proposal/Price Quote</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.stage" id="stage_oppor5">
<field name="name">Negotiation/Review</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.stage" id="stage_oppor6">
<field name="name">Closed Won</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.stage" id="stage_oppor7">
<field name="name">Closed Lost</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<!-- Case Resource(type_id) -->
<record model="crm.case.resource.type" id="type_oppor1">
<field name="name">Existing Business</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.resource.type" id="type_oppor2">
<field name="name">New Business</field>
<field name="section_id" ref="section_sales_department"/>
<field name="object_id" search="[('model','=','crm.opportunity')]" model="ir.model"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
</data>

View File

@ -7,10 +7,11 @@
<!--For Opportunity-->
<record id="crm_case_construstazunits0" model="crm.opportunity">
<record id="crm_case_construstazunits0" model="crm.lead">
<field eval="60" name="probability"/>
<field name="partner_address_id" ref="base.res_partner_address_zen"/>
<field eval="1" name="active"/>
<field name="type">opportunity</field>
<field name="type_id" ref="crm.type_oppor1"/>
<field name="partner_id" ref="base.res_partner_3"/>
<field eval="&quot;3&quot;" name="priority"/>
@ -22,9 +23,10 @@
<field name="stage_id" ref="crm.stage_oppor3"/>
<field eval="&quot;CONS TRUST (AZ) 529701 - 1000 units&quot;" name="name"/>
</record>
<record id="crm_case_rdroundfundingunits0" model="crm.opportunity">
<record id="crm_case_rdroundfundingunits0" model="crm.lead">
<field name="partner_address_id" ref="base.res_partner_address_15"/>
<field eval="1" name="active"/>
<field name="type">opportunity</field>
<field name="type_id" ref="crm.type_oppor2"/>
<field name="partner_id" ref="base.res_partner_11"/>
<field eval="&quot;3&quot;" name="priority"/>
@ -37,10 +39,11 @@
<field name="stage_id" ref="crm.stage_oppor1"/>
<field eval="&quot;3rd Round Funding - 1000 units &quot;" name="name"/>
</record>
<record id="crm_case_mediapoleunits0" model="crm.opportunity">
<record id="crm_case_mediapoleunits0" model="crm.lead">
<field eval="10" name="probability"/>
<field name="partner_address_id" ref="base.res_partner_address_3"/>
<field eval="1" name="active"/>
<field name="type">opportunity</field>
<field name="type_id" ref="crm.type_oppor1"/>
<field name="partner_id" ref="base.res_partner_8"/>
<field eval="&quot;3&quot;" name="priority"/>
@ -54,10 +57,11 @@
<field eval="&quot;Mediapole - 5000 units&quot;" name="name"/>
<field eval="&quot;info@mediapole.net&quot;" name="email_from"/>
</record>
<record id="crm_case_abcfuelcounits0" model="crm.opportunity">
<record id="crm_case_abcfuelcounits0" model="crm.lead">
<field eval="40" name="probability"/>
<field name="partner_address_id" ref="base.res_partner_address_1"/>
<field eval="1" name="active"/>
<field name="type">opportunity</field>
<field name="type_id" ref="crm.type_oppor1"/>
<field name="partner_id" ref="base.res_partner_9"/>
<field eval="&quot;3&quot;" name="priority"/>
@ -70,10 +74,11 @@
<field eval="&quot;ABC FUEL CO 829264 - 1000 units &quot;" name="name"/>
<field eval="&quot;info@opensides.be&quot;" name="email_from"/>
</record>
<record id="crm_case_dirtminingltdunits0" model="crm.opportunity">
<record id="crm_case_dirtminingltdunits0" model="crm.lead">
<field eval="80" name="probability"/>
<field name="partner_address_id" ref="base.res_partner_address_wong"/>
<field eval="1" name="active"/>
<field name="type">opportunity</field>
<field name="partner_id" ref="base.res_partner_maxtor"/>
<field eval="&quot;3&quot;" name="priority"/>
<field name="user_id" ref="base.user_root"/>

View File

@ -6,7 +6,7 @@
id="act_crm_opportunity_crm_meeting_new"
name="Meetings"
res_model="crm.meeting"
src_model="crm.opportunity"
src_model="crm.lead"
view_mode="tree,form,calendar,"
context="{'default_duration': 4.0, 'default_opportunity_id': active_id}"
domain="[('user_id','=',uid), ('opportunity_id', '=', active_id)]"
@ -16,39 +16,46 @@
id="act_crm_opportunity_crm_phonecall_new"
name="Phone calls"
res_model="crm.phonecall"
src_model="crm.opportunity"
src_model="crm.lead"
view_mode="calendar,tree,form"
context="{'default_duration': 1.0}"
domain="[('user_id','=',uid)]"
context="{'default_duration': 1.0 ,'default_opportunity_id': active_id}"
domain="[('user_id','=',uid),('opportunity_id', '=', active_id)]"
view_type="form"/>
<record model="ir.actions.act_window" id="crm_case_category_act_oppor11">
<field name="name">Opportunities</field>
<field name="res_model">crm.opportunity</field>
<field name="res_model">crm.lead</field>
<field name="view_mode">tree,form,graph</field>
<field name="domain">[('type','=','opportunity')]</field>
<field name="context">{'search_default_current':1}</field>
<field name="view_id" ref="crm_case_tree_view_oppor"/>
<field name="search_view_id" ref="crm.view_crm_case_opportunities_filter"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_tree_view_oppor11">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="crm_case_tree_view_oppor"/>
<field name="act_window_id" ref="crm_case_category_act_oppor11"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_form_view_oppor11">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="crm_case_form_view_oppor"/>
<field name="act_window_id" ref="crm_case_category_act_oppor11"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_graph_view_oppor11">
<field name="sequence" eval="4"/>
<field name="view_mode">graph</field>
<field name="view_id" ref="crm_case_graph_view_opportunity"/>
<field name="act_window_id" ref="crm_case_category_act_oppor11"/>
</record>
<menuitem id="base.menu_sales" name="Sales"
parent="base.menu_base_partner" sequence="1" />
<menuitem name="Opportunities" id="menu_crm_case_opp"
parent="base.menu_sales" action="crm_case_category_act_oppor11"
sequence="2" />

View File

@ -9,8 +9,8 @@
<field name="res_model">crm.case.categ</field>
<field name="view_type">form</field>
<field name="view_id" ref="crm.crm_case_categ_tree-view"/>
<field name="domain">[('object_id.model', '=', 'crm.opportunity')]</field>
<field name="context">{'object_id':'crm.opportunity'}</field>
<field name="domain">[('object_id.model', '=', 'crm.lead')]</field>
<field name="context">{'object_id':'crm.lead'}</field>
</record>
<menuitem action="crm_opportunity_categ_action"
@ -24,8 +24,8 @@
<field name="res_model">crm.case.stage</field>
<field name="view_type">form</field>
<field name="view_id" ref="crm.crm_case_stage_tree"/>
<field name="domain">[('object_id.model', '=', 'crm.opportunity')]</field>
<field name="context">{'object_id':'crm.opportunity'}</field>
<field name="domain">[('object_id.model', '=', 'crm.lead')]</field>
<field name="context">{'object_id':'crm.lead'}</field>
</record>
<menuitem action="crm_opportunity_stage_act"
@ -39,8 +39,8 @@
<field name="res_model">crm.case.resource.type</field>
<field name="view_type">form</field>
<field name="view_id" ref="crm.crm_case_resource_type_tree"/>
<field name="domain">[('object_id.model', '=', 'crm.opportunity')]</field>
<field name="context">{'object_id':'crm.opportunity'}</field>
<field name="domain">[('object_id.model', '=', 'crm.lead')]</field>
<field name="context">{'object_id':'crm.lead'}</field>
</record>
<menuitem action="crm_opportunity_resource_act"
@ -51,7 +51,7 @@
<record model="ir.ui.view" id="crm_case_form_view_oppor">
<field name="name">Opportunities</field>
<field name="model">crm.opportunity</field>
<field name="model">crm.lead</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Opportunities">
@ -62,7 +62,7 @@
<field name="stage_id" nolabel="1"
on_change="onchange_stage_id(stage_id)"
widget="selection"
domain="[('object_id.model', '=', 'crm.opportunity')]" />
domain="[('object_id.model', '=', 'crm.lead')]" />
<button name="stage_previous"
states="open,pending" type="object"
icon="gtk-go-back" string="" />
@ -86,7 +86,7 @@
</group>
<notebook colspan="4">
<page string="Opportunity">
<group col="3" colspan="2">
<group col="4" colspan="2">
<separator colspan="4" string="Contacts"/>
<field name="partner_id" select="1"
on_change="onchange_partner_id(partner_id, email_from)"
@ -103,7 +103,7 @@
<field name="section_id" colspan="1" widget="selection"/>
<field name="categ_id" select="1" groups="base.group_extended"
string="Category" widget="selection"
domain="[('object_id.model', '=', 'crm.opportunity')]" />
domain="[('object_id.model', '=', 'crm.lead')]" />
</group>
<separator colspan="4" string="Details"/>
@ -124,7 +124,7 @@
icon="gtk-media-pause" />
<button name="case_close" string="Mark Won"
states="open,draft,pending" type="object"
icon="gtk-close" />
icon="gtk-apply" />
<button name="case_cancel" string="Mark Lost"
states="draft,open,pending" type="object"
icon="gtk-cancel" />
@ -153,7 +153,12 @@
<field name="ref"/>
<field name="ref2"/>
<field name="log_ids" nolabel="1" colspan="4">
<form string="Actions">
<tree string="Logs">
<field name="name" colspan="4"/>
<field name="date"/>
<field name="user_id"/>
</tree>
<form string="Logs">
<separator string="Action Information" colspan="4"/>
<field name="name" colspan="4"/>
<field name="date"/>
@ -165,23 +170,19 @@
<group colspan="4">
<field colspan="4" name="email_cc" string="CC"/>
</group>
<field name="history_line" colspan="4" nolabel="1" mode="form,tree">
<field name="message_ids" colspan="4" nolabel="1" mode="form,tree">
<form string="Communication history">
<group col="7" colspan="4">
<group col="6" colspan="4">
<field name="date"/>
<field name="email_to"/>
<field name="email_from"/>
<button
string="Add a CC"
name="%(crm.action_view_crm_email_add_cc_wizard)d"
icon="gtk-add" type="action"/>
</group>
</group>
<newline/>
<field name="description" colspan="4" nolabel="1"/>
<button colspan="4"
string="Reply to Last Email"
name="%(action_crm_send_mail)d"
context="{'mail':'reply', 'model': 'crm.opportunity'}"
context="{'mail':'reply', 'model': 'crm.lead'}"
icon="gtk-undo" type="action" />
</form>
<tree string="Communication history">
@ -192,10 +193,10 @@
</field>
<button colspan="2" string="Send New Email"
name="%(action_crm_send_mail)d"
context="{'mail':'new', 'model': 'crm.opportunity'}"
context="{'mail':'new', 'model': 'crm.lead'}"
icon="gtk-go-forward" type="action" />
<button colspan="2" string="Forward to Partner"
name="%(crm_opportunity_forward_to_partner_act)d"
name="%(crm_lead_forward_to_partner_act)d"
icon="gtk-go-forward" type="action" />
</page>
</notebook>
@ -207,7 +208,7 @@
<record model="ir.ui.view" id="crm_case_tree_view_oppor">
<field name="name">Opportunities Tree</field>
<field name="model">crm.opportunity</field>
<field name="model">crm.lead</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Opportunities" colors="blue:state=='pending';grey:state in ('cancel','done')">
@ -248,7 +249,7 @@
<record model="ir.ui.view" id="crm_case_graph_view_opportunity">
<field name="name">CRM - Opportunity Graph</field>
<field name="model">crm.opportunity</field>
<field name="model">crm.lead</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<graph string="Opportunity by Categories" type="bar" orientation="horizontal">
@ -263,13 +264,13 @@
<record id="view_crm_case_opportunities_filter" model="ir.ui.view">
<field name="name">CRM - Opportunities Search</field>
<field name="model">crm.opportunity</field>
<field name="model">crm.lead</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Search Opportunities">
<filter icon="terp-project"
string="Current"
default="1"
name="current"
domain="[('state','in',('draft','open'))]"/>
<filter icon="terp-project"
string="Open"
@ -305,7 +306,7 @@
help="My section" />
</field>
<newline/>
<group expand="1" string="Group By..." colspan="16">
<group expand="0" string="Group By..." colspan="16">
<filter string="Stage" icon="terp-crm" domain="[]"
context="{'group_by':'stage_id'}" />
<filter string="Priority" icon="terp-crm" domain="[]"
@ -327,5 +328,60 @@
</search>
</field>
</record>
<!-- Partners inherited form -->
<record id="view_opp_partner_info_form" model="ir.ui.view">
<field name="name">res.partner.opportunity.info.inherit</field>
<field name="model">res.partner</field>
<field name="type">form</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<field name="phonecall_ids" position="after">
<field name="opportunity_ids" colspan="4" nolabel="1" domain="[('type', '=', 'opportunity')]">
<tree string="Opportunities" colors="blue:state=='pending';grey:state in ('cancel', 'done')">
<field name="create_date"/>
<field name="partner_name"/>
<field name="name"/>
<field name="email_from"/>
<field name="phone"/>
<field name="categ_id" invisible="1"/>
<field name="type_id" invisible="1"/>
<field name="referred" invisible="1"/>
<field name="stage_id"/>
<button name="stage_previous" string="Previous"
states="open,pending" type="object" icon="gtk-go-back" />
<button name="stage_next" string="Next"
states="open,pending" type="object"
icon="gtk-go-forward" />
<field name="section_id"
invisible="context.get('invisible_section', True)" />
<field name="user_id" />
<field name="state" />
<button name="case_open" string="Open"
states="draft,pending" type="object"
icon="gtk-go-forward" />
<button name="case_close" string="Close"
states="open,draft,pending" type="object"
icon="gtk-close" />
<button string="Convert to Opportunity"
name="convert_opportunity"
states="draft,open,pending" icon="gtk-index"
type="object" />
<button name="case_escalate" string="Escalate"
states="open,draft,pending" type="object"
icon="gtk-go-up" />
<button name="case_cancel" string="Cancel"
states="draft,open,pending" type="object"
icon="gtk-cancel" />
</tree>
</field>
</field>
</field>
</record>
</data>
</openerp>

View File

@ -19,43 +19,108 @@
#
##############################################################################
from crm import crm_case
from osv import fields, osv
from tools.translate import _
import crm
import time
class crm_phonecall(osv.osv):
class crm_phonecall(osv.osv, crm_case):
""" Phonecall Cases """
_name = "crm.phonecall"
_description = "Phonecall Cases"
_order = "id desc"
_inherit = 'crm.case'
_inherit = 'mailgate.thread'
_columns = {
'duration': fields.float('Duration'),
# From crm.case
'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.many2one('res.users', 'Responsible'),
'partner_id': fields.many2one('res.partner', 'Partner'),
'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \
domain="[('partner_id','=',partner_id)]"),
'company_id': fields.many2one('res.company', 'Company'),
'description': fields.text('Description'),
'state': fields.selection([
('draft', 'Draft'),
('open', 'Todo'),
('cancel', 'Cancelled'),
('done', 'Closed'),
('pending', 'Pending'),
], 'State', size=16, readonly=True,
help='The state is set to \'Draft\', when a case is created.\
\nIf the case is in progress the state is set to \'Open\'.\
\nWhen the case is over, the state is set to \'Done\'.\
\nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
'email_from': fields.char('Email', size=128, help="These people will receive email."),
'stage_id': fields.many2one('crm.case.stage', 'Stage', \
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.phonecall')]"),
'date_open': fields.datetime('Opened', readonly=True),
# phonecall fields
'duration': fields.float('Duration'),
'categ_id': fields.many2one('crm.case.categ', 'Category', \
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.phonecall')]"),
'partner_phone': fields.char('Phone', size=32),
'partner_contact': fields.related('partner_address_id', 'name',\
type="char", string="Contact", size=128),
'partner_mobile': fields.char('Mobile', size=32),
'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
'canal_id': fields.many2one('res.partner.canal', 'Channel',\
('object_id.model', '=', 'crm.phonecall')]"),
'partner_phone': fields.char('Phone', size=32),
'partner_contact': fields.related('partner_address_id', 'name', \
type="char", string="Contact", size=128),
'partner_mobile': fields.char('Mobile', size=32),
'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
'canal_id': fields.many2one('res.partner.canal', 'Channel', \
help="The channels represent the different communication\
modes available with the customer." \
" With each commercial opportunity, you can indicate\
the canall which is this opportunity source."),
'date_closed': fields.datetime('Closed', readonly=True),
'date': fields.datetime('Date'),
'opportunity_id': fields.many2one ('crm.opportunity', 'Opportunity'),
the canall which is this opportunity source."),
'date_closed': fields.datetime('Closed', readonly=True),
'date': fields.datetime('Date'),
'opportunity_id': fields.many2one ('crm.lead', 'Opportunity'),
}
_defaults = {
'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
'state': lambda *a: 'draft',
'active': lambda *a: 1,
}
# From crm.case
def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
res = super(crm_phonecall, self).onchange_partner_address_id(cr, uid, ids, add, email)
res.setdefault('value', {})
if add:
address = self.pool.get('res.partner.address').browse(cr, uid, add)
res['value']['partner_phone'] = address.phone
res['value']['partner_mobile'] = address.mobile
return res
def case_close(self, cr, uid, ids, *args):
"""Overrides close for crm_case for setting close date
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case Ids
@param *args: Tuple Value for additional Params
"""
res = super(crm_phonecall, self).case_close(cr, uid, ids, args)
self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
return res
def case_open(self, cr, uid, ids, *args):
"""Overrides cancel for crm_case for setting Open Date
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case's Ids
@param *args: Give Tuple Value
"""
res = super(crm_phonecall, self).case_open(cr, uid, ids, *args)
self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
return res
def action_make_meeting(self, cr, uid, ids, context=None):
"""
@ -86,36 +151,39 @@ class crm_phonecall(osv.osv):
id3 = data_obj.browse(cr, uid, id3, context=context).res_id
context = {
'default_phonecall_id': phonecall.id,
'default_partner_id': phonecall.partner_id and phonecall.partner_id.id or False,
'default_email': phonecall.email_from ,
'default_phonecall_id': phonecall.id,
'default_partner_id': phonecall.partner_id and phonecall.partner_id.id or False,
'default_email': phonecall.email_from ,
'default_name': phonecall.name
}
value = {
'name': _('Meetings'),
'domain' : "[('user_id','=',%s)]" % (uid),
'context': context,
'view_type': 'form',
'view_mode': 'calendar,form,tree',
'res_model': 'crm.meeting',
'view_id': False,
'views': [(id1, 'calendar'), (id2, 'form'), (id3, 'tree')],
'type': 'ir.actions.act_window',
'search_view_id': res['res_id'],
'name': _('Meetings'),
'domain' : "[('user_id','=',%s)]" % (uid),
'context': context,
'view_type': 'form',
'view_mode': 'calendar,form,tree',
'res_model': 'crm.meeting',
'view_id': False,
'views': [(id1, 'calendar'), (id2, 'form'), (id3, 'tree')],
'type': 'ir.actions.act_window',
'search_view_id': res['res_id'],
'nodestroy': True
}
return value
def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
res = super(crm_phonecall, self).onchange_partner_address_id(cr, uid, ids, add, email)
res.setdefault('value', {})
if add:
address = self.pool.get('res.partner.address').browse(cr, uid, add)
res['value']['partner_phone'] = address.mobile
res['value']['partner_mobile'] = address.phone
return res
crm_phonecall()
class res_partner(osv.osv):
""" Inherits partner and adds Phonecalls information in the partner form """
_inherit = 'res.partner'
_columns = {
'phonecall_ids': fields.one2many('crm.phonecall', 'partner_id', 'Phonecalls'),
}
res_partner()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -62,7 +62,7 @@
<record model="ir.actions.act_window.view" id="action_crm_tag_tree_phone_incoming0">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="crm_case_phone_tree_view"/>
<field name="view_id" ref="crm_case_inbound_phone_tree_view"/>
<field name="act_window_id" ref="crm_case_categ_phone_incoming0"/>
</record>
@ -76,7 +76,7 @@
<record model="ir.actions.act_window.view" id="action_crm_tag_form_phone_incoming0">
<field name="sequence" eval="3"/>
<field name="view_mode">form</field>
<field name="view_id" ref="crm_case_phone_form_view"/>
<field name="view_id" ref="crm_case_inbound_phone_form_view"/>
<field name="act_window_id" ref="crm_case_categ_phone_incoming0"/>
</record>

View File

@ -53,7 +53,7 @@
type="action" attrs="{'invisible':[('opportunity_id','!=',False)]}" />
<button string="Meeting"
states="draft,open,pending"
name="action_make_meeting" type="action" />
name="action_make_meeting" type="object" />
<field name="state"/>
<button name="case_open" string="Open" states="draft,pending" type="object" icon="gtk-go-forward"/>
<button name="case_close" string="Held" states="open,draft,pending" type="object" icon="gtk-jump-to"/>
@ -72,7 +72,7 @@
<field name="arch" type="xml">
<form string="Phone Call">
<group colspan="4" col="7">
<field name="name" string="Call Summary"/>
<field name="name" string="Summary"/>
<field name="date" string="Planned Date"/>
<field name="user_id"/>
<button string="Schedule a Meeting"
@ -137,6 +137,93 @@
</form>
</field>
</record>
<!--Inbound Phonecalls Form View -->
<record model="ir.ui.view" id="crm_case_inbound_phone_form_view">
<field name="name">CRM - Inbound Phone Call Form</field>
<field name="model">crm.phonecall</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Phone Call">
<group colspan="4" col="7">
<field name="name" string="Summary"/>
<field name="date" string="Planned Date"/>
<field name="user_id"/>
<button string="Schedule a Meeting"
name="action_make_meeting"
icon="gtk-redo"
type="object" />
<field name="partner_phone"/>
<field name="duration"/>
<field name="section_id" colspan="1" widget="selection" />
<button string="Convert to Opportunity"
name="%(phonecall2opportunity_act)d"
icon="gtk-index" type="action"
attrs="{'invisible':[('opportunity_id','!=',False)]}" />
<label colspan="6" string=""/>
<button string="Schedule Other Call"
icon="gtk-redo"
name="%(phonecall_to_phonecall_act)d"
type="action" />
</group>
<group col="3" colspan="2">
<separator colspan="3" string="Contacts" />
<field name="partner_id"
on_change="onchange_partner_id(partner_id, email_from)" />
<button string="Create a Partner"
icon="terp-crm"
name="%(action_crm_phonecall2partner)d"
type="action"
attrs="{'invisible':[('partner_id','!=',False)]}" />
<newline/>
<field name="partner_address_id" string="Contact"
on_change="onchange_partner_address_id(partner_address_id, email_from)" />
<newline/>
<field name="partner_phone" />
<newline/>
<field name="partner_mobile" />
</group>
<group col="2" colspan="2">
<separator colspan="2" string="Categorization" />
<field name="priority"/>
<field name="opportunity_id"/>
</group>
<separator string="Description" colspan="4" />
<field name="description" nolabel="1" colspan="4" />
<separator colspan="4" />
</form>
</field>
</record>
<!--Inbound Phonecalls Tree View -->
<record model="ir.ui.view" id="crm_case_inbound_phone_tree_view">
<field name="name">CRM - Inbound Phone Calls Tree</field>
<field name="model">crm.phonecall</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Phone Calls" colors="grey:state in ('cancel','done');blue:state in ('pending',)">
<field name="date" string="Date"/>
<field name="name" string="Call Summary"/>
<field name="partner_id" string="Partner"/>
<field name="partner_contact"/>
<field name="partner_phone"/>
<field name="user_id"/>
<field name="state" invisible="1"/>
<button string="Opportunity"
name="%(phonecall2opportunity_act)d"
states="draft,open,pending"
icon="gtk-convert"
type="action" attrs="{'invisible':[('opportunity_id','!=',False)]}" />
<button string="Meeting"
states="draft,open,pending" icon="gtk-redo"
name="action_make_meeting" type="object" />
</tree>
</field>
</record>
<!-- Phonecalls Calendar View -->
@ -190,7 +277,7 @@
help="My section" />
</field>
<newline/>
<group expand="1" string="Group By..." colspan="4">
<group expand="0" string="Group By..." colspan="4">
<filter string="Partner" icon="terp-crm" domain="[]"
context="{'group_by':'partner_id'}" />
<filter string="Responsible" icon="terp-crm"
@ -204,5 +291,20 @@
</search>
</field>
</record>
<!-- Partners inherited form -->
<record id="view_phonecall_partner_info_form" model="ir.ui.view">
<field name="name">res.partner.phonecall.info.inherit</field>
<field name="model">res.partner</field>
<field name="type">form</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<field name="meeting_ids" position="after">
<field name="phonecall_ids" colspan="4" nolabel="1" />
</field>
</field>
</record>
</data>
</openerp>

View File

@ -37,39 +37,26 @@ class crm_segmentation(osv.osv):
'description': fields.text('Description'),
'categ_id': fields.many2one('res.partner.category', 'Partner Category',\
required=True, help='The partner category that will be \
added to partners that match the segmentation criterions after computation.'),
'exclusif': fields.boolean('Exclusive', help='Check if the category is \
limited to partners that match the segmentation criterions.\
If checked, remove the category from partners that doesn\'t \
match segmentation criterions'),
added to partners that match the segmentation criterions after computation.'),
'exclusif': fields.boolean('Exclusive', help='Check if the category is limited to partners that match the segmentation criterions.\
\nIf checked, remove the category from partners that doesn\'t match segmentation criterions'),
'state': fields.selection([('not running','Not Running'),\
('running','Running')], 'Execution Status', readonly=True),
'partner_id': fields.integer('Max Partner ID processed'),
'segmentation_line': fields.one2many('crm.segmentation.line', \
'segmentation_id', 'Criteria', required=True),
'som_interval': fields.integer('Days per Periode', help="A period is the \
average number of days between two cycle of sale or\
purchase for this segmentation. It's mainly used to\
detect if a partner has not purchased or buy for a too \
long time, so we suppose that his state of mind has \
decreased because he probably bought goods to another \
supplier. Use this functionality for recurring businesses."),
'som_interval_max': fields.integer('Max Interval', help="The computation \
is made on all events that occured during this \
interval, the past X periods."),
'som_interval_decrease': fields.float('Decrease (0>1)', help="If the \
partner has not purchased (or bought) \
during a period, decrease the state of \
mind by this factor. It\'s a multiplication"),
'som_interval': fields.integer('Days per Periode', help="A period is the average number of days between two cycle of sale or purchase for this segmentation.\
\nIt's mainly used to detect if a partner has not purchased or buy for a too long time, \
\nso we suppose that his state of mind has decreased because he probably bought goods to another supplier. \
\nUse this functionality for recurring businesses."),
'som_interval_max': fields.integer('Max Interval', help="The computation is made on all events that occured during this interval, the past X periods."),
'som_interval_decrease': fields.float('Decrease (0>1)', help="If the "\
"partner has not purchased (or bought) during a "\
"period, decrease the state of mind by this factor. It\'s a multiplication"),
'som_interval_default': fields.float('Default (0=None)', help="Default \
state of mind for period preceeding the \
'Max Interval' computation. This is the \
starting state of mind by default if the \
partner has no event."),
'sales_purchase_active': fields.boolean('Use The Sales Purchase Rules',\
help='Check if you want to use this tab as\
part of the segmentation rule. If not \
checked, the criteria beneath will be ignored')
state of mind for period preceeding the 'Max Interval' computation. \
This is the starting state of mind by default if the partner has no event."),
'sales_purchase_active': fields.boolean('Use The Sales Purchase Rules', help='Check if you want to use this tab as part of the segmentation rule. If not checked, the criteria beneath will be ignored')
}
_defaults = {
'partner_id': lambda *a: 0,

View File

@ -1,17 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<menuitem id="menu_crm_configuration" name="Cases"
<menuitem id="base.menu_crm_configuration" name="Cases"
parent="base.menu_base_config" sequence="0" groups="base.group_extended"/>
<!-- Case Sections Form View -->
<menuitem id="base.next_id_64" name="Reporting"
parent="base.menu_base_partner" sequence="8" />
<!-- Case Sections Form View -->
<record id="crm_case_section_view_form" model="ir.ui.view">
<field name="name">crm.case.section.form</field>
<field name="model">crm.case.section</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Case Section">
<form string="Sales Team">
<group col="6" colspan="4">
<field name="name" select="1" colspan="4"/>
<field name="code" select="1"/>
@ -19,22 +22,24 @@
<field name="resource_calendar_id" select="2"/>
</group>
<notebook colspan="4">
<page string="Case section">
<group col="2" colspan="2">
<page string="Sales Team">
<group col="2" colspan="1">
<separator string="Responsible" colspan="2"/>
<field name="parent_id" select="2" widget="selection"/>
<field name="user_id" select="2"/>
</group>
<group col="2" colspan="2">
<group col="2" colspan="1">
<separator string="Contact Information" colspan="2"/>
<field name="server_id" select="2"/>
<field name="reply_to" select="2"/>
</group>
<group col="2" colspan="2">
<separator string="Section Property" colspan="2"/>
<group col="2" colspan="1">
<separator string="Sales Team Property" colspan="2"/>
<field name="active" select="2"/>
<field name="allow_unlink" select="2"/>
</group>
<separator string="Members List" colspan="4"/>
<field name="member_ids" nolabel="1" colspan="4"/>
<separator string="Note" colspan="4"/>
<field name="note" select="1" colspan="4" nolabel="1"/>
</page>
@ -51,7 +56,7 @@
<field name="type">tree</field>
<field name="field_parent">child_ids</field>
<field name="arch" type="xml">
<tree string="Case Section">
<tree string="Sales Team">
<field name="name" select="1"/>
<field name="code" select="1"/>
<field name="user_id" select="1"/>
@ -62,7 +67,7 @@
<!-- Case Sections Action -->
<record id="crm_case_section_act" model="ir.actions.act_window">
<field name="name">Sections</field>
<field name="name">Sales Team</field>
<field name="res_model">crm.case.section</field>
<field name="view_type">form</field>
<field name="view_id" ref="crm_case_section_view_tree"/>
@ -115,7 +120,7 @@
<field name="view_id" ref="crm_case_stage_tree"/>
</record>
<menuitem id="menu_crm_case_stage" name="Stages" parent="crm.menu_crm_configuration" groups="base.group_extended"/>
<menuitem id="menu_crm_case_stage" name="Stages" parent="base.menu_crm_configuration" groups="base.group_extended"/>
<!-- Case Categories Form View -->
@ -155,11 +160,11 @@
<field name="view_type">form</field>
<field name="view_id" ref="crm_case_categ_tree-view"/>
</record>
<menuitem id="menu_crm_case_categ" name="Categories" parent="crm.menu_crm_configuration" groups="base.group_extended"/>
<menuitem id="menu_crm_case_categ" name="Categories" parent="base.menu_crm_configuration" groups="base.group_extended"/>
<menuitem action="crm_case_section_act"
id="menu_crm_case_section_act"
parent="crm.menu_crm_configuration" />
parent="base.menu_crm_configuration" />
<!-- Resource Type of case Tree View -->
@ -201,7 +206,7 @@
<field name="view_id" ref="crm_case_resource_type_tree"/>
</record>
<menuitem id="menu_crm_case_resource_type" name="Resource Type"
parent="crm.menu_crm_configuration" groups="base.group_extended" />
parent="base.menu_crm_configuration" groups="base.group_extended" />
<record id="crm_case_section_act_tree" model="ir.actions.act_window">
<field name="name">Cases by section</field>
@ -254,7 +259,7 @@
<record id="crm_case_calendar-view" model="ir.ui.view">
<field name="name">crm.case.calendar</field>
<field name="model">crm.case</field>
<field name="model">mailgate.thread</field>
<field name="type">calendar</field>
<field name="arch" type="xml">
<calendar color="user_id" date_start="create_date"
@ -271,7 +276,7 @@
<record id="crm_case_tree-view" model="ir.ui.view">
<field name="name">crm.case.tree</field>
<field name="model">crm.case</field>
<field name="model">mailgate.thread</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree colors="red:date_deadline&lt;current_date and state=='open';black:state in ('draft', 'cancel','done','pending')" string="Cases">
@ -309,7 +314,7 @@
<record id="crm_case-view" model="ir.ui.view">
<field name="name">crm.case.form</field>
<field name="model">crm.case</field>
<field name="model">mailgate.thread</field>
<field name="type">form</field>
<field name="priority" eval="1"/>
<field name="arch" type="xml">
@ -390,16 +395,12 @@
<group colspan="4">
<field colspan="4" name="email_cc" string="CC"/>
</group>
<field name="history_line" colspan="4" nolabel="1" mode="form,tree">
<field name="message_ids" colspan="4" nolabel="1" mode="form,tree">
<form string="Communication history">
<group col="7" colspan="4">
<group col="6" colspan="4">
<field name="date"/>
<field name="email_to"/>
<field name="email_from"/>
<button
string="Add a CC"
name="%(crm.action_view_crm_email_add_cc_wizard)d"
icon="gtk-add" type="action"/>
</group>
<newline/>
<field name="description" colspan="4" nolabel="1"/>
@ -427,9 +428,10 @@
<!-- Case Search View -->
<record id="view_crm_case_filter" model="ir.ui.view">
<field name="name">crm.case.select</field>
<field name="model">crm.case</field>
<field name="model">mailgate.thread</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Search Case">
@ -461,7 +463,7 @@
<record id="crm_case_categ0-act" model="ir.actions.act_window">
<field name="name">Cases</field>
<field name="res_model">crm.case</field>
<field name="res_model">mailgate.thread</field>
<field name="view_type">form</field>
<field name="view_id" ref="crm_case_tree-view"/>
<field name="search_view_id" ref="view_crm_case_filter"/>
@ -469,7 +471,7 @@
<record id="crm_case_categ0-act_open" model="ir.actions.act_window">
<field name="name">Open Cases</field>
<field name="res_model">crm.case</field>
<field name="res_model">mailgate.thread</field>
<field name="view_type">form</field>
<field name="domain">
[('state','&lt;&gt;','done'),('state','&lt;&gt;','cancel'),('state','&lt;&gt;','pending')]
@ -479,7 +481,7 @@
<record id="crm_case_section_open_act" model="ir.actions.act_window">
<field name="name">Cases</field>
<field name="res_model">crm.case</field>
<field name="res_model">mailgate.thread</field>
<field name="domain">[('section_id','child_of',[active_id])]</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar</field>
@ -660,7 +662,7 @@
<menuitem action="crm_segmentation_tree-act"
id="menu_crm_segmentation-act"
groups="base.group_extended"
parent="crm.menu_crm_configuration" />
parent="base.menu_crm_configuration" />
<record model="ir.ui.view" id="view_users_form_simple_modif_inherited1">
<field name="name">view.users.form.crm.modif.inherited1</field>

View File

@ -8,7 +8,7 @@
<record id="process_process_contractprocess0" model="process.process">
<field eval="1" name="active"/>
<field name="model_id" ref="crm.model_crm_case"/>
<field name="model_id" ref="mail_gateway.model_mailgate_thread"/>
<field eval="&quot;&quot;&quot;Contract&quot;&quot;&quot;" name="name"/>
</record>
@ -17,7 +17,7 @@
-->
<record id="process_node_leads0" model="process.node">
<field name="model_id" ref="crm.model_crm_case"/>
<field name="model_id" ref="mail_gateway.model_mailgate_thread"/>
<field eval="&quot;&quot;&quot;state&quot;&quot;&quot;" name="kind"/>
<field eval="&quot;&quot;&quot;Leads&quot;&quot;&quot;" name="name"/>
<field eval="&quot;&quot;&quot;Very first contact with new prospect&quot;&quot;&quot;" name="note"/>
@ -37,7 +37,7 @@
</record>
<record id="process_node_opportunities0" model="process.node">
<field name="model_id" ref="crm.model_crm_case"/>
<field name="model_id" ref="mail_gateway.model_mailgate_thread"/>
<field eval="&quot;&quot;&quot;state&quot;&quot;&quot;" name="kind"/>
<field eval="&quot;&quot;&quot;Opportunities&quot;&quot;&quot;" name="name"/>
<field eval="&quot;&quot;&quot;When a real project/opportunity is detected&quot;&quot;&quot;" name="note"/>
@ -47,7 +47,7 @@
</record>
<record id="process_node_meeting0" model="process.node">
<field name="model_id" ref="crm.model_crm_case"/>
<field name="model_id" ref="mail_gateway.model_mailgate_thread"/>
<field eval="&quot;&quot;&quot;state&quot;&quot;&quot;" name="kind"/>
<field eval="&quot;&quot;&quot;Meeting&quot;&quot;&quot;" name="name"/>
<field eval="&quot;&quot;&quot;Schedule a normal or phone meeting&quot;&quot;&quot;" name="note"/>

View File

@ -19,14 +19,8 @@
#
##############################################################################
#import report_businessopp
import crm_report
import crm_claim_report
import crm_lead_report
import crm_phonecall_report
import crm_fundraising_report
import crm_opportunity_report
import crm_helpdesk_report
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,90 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import fields,osv
import tools
import crm_report
class crm_claim_report(osv.osv):
""" CRM Claim Report"""
_name = "crm.claim.report"
_auto = False
_inherit = "crm.case.report"
_description = "CRM Claim Report"
_columns = {
'delay_close': fields.float('Delay to close', digits=(16,2),readonly=True, group_operator="avg",help="Number of Days to close the case"),
'stage_id': fields.many2one ('crm.case.stage', 'Stage', \
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.claim')]", readonly=True),
'categ_id': fields.many2one('crm.case.categ', 'Category',\
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.claim')]", readonly=True),
'partner_id': fields.many2one('res.partner', 'Partner', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'priority': fields.selection(crm_report.AVAILABLE_PRIORITIES, 'Priority'),
'type_id': fields.many2one('crm.case.resource.type', 'Claim Type',\
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.claim')]"),
}
def init(self, cr):
""" Display Number of cases And Section Name
@param cr: the current row, from the database cursor,
"""
tools.drop_view_if_exists(cr, 'crm_claim_report')
cr.execute("""
create or replace view crm_claim_report as (
select
min(c.id) as id,
to_char(c.create_date, 'YYYY') as name,
to_char(c.create_date, 'MM') as month,
to_char(c.create_date, 'YYYY-MM-DD') as day,
c.state,
c.user_id,
c.stage_id,
c.section_id,
c.partner_id,
c.company_id,
c.categ_id,
count(*) as nbr,
0 as avg_answers,
0.0 as perc_done,
0.0 as perc_cancel,
c.priority as priority,
c.type_id as type_id,
date_trunc('day',c.create_date) as create_date,
avg(extract('epoch' from (c.date_closed-c.create_date)))/(3600*24) as delay_close
from
crm_claim c
group by to_char(c.create_date, 'YYYY'), to_char(c.create_date, 'MM'), \
c.state, c.user_id,c.section_id, c.stage_id,\
c.categ_id,c.partner_id,c.company_id,c.create_date,to_char(c.create_date, 'YYYY-MM-DD')
,c.priority,c.type_id,c.som
)""")
crm_claim_report()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,135 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- CRM Claim Report Tree View -->
<record id="view_report_crm_claim_tree" model="ir.ui.view">
<field name="name">crm.claim.report.tree</field>
<field name="model">crm.claim.report</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Claims">
<field name="name" invisible="1"/>
<field name="month" invisible="1"/>
<field name="section_id" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="partner_id" invisible="1"/>
<field name="day" invisible="1"/>
<field name="nbr" string="#Claim" sum="#Claim"/>
<field name="delay_close" avg="Avg Closing Delay"/>
<field name="state" invisible="1"/>
<field name="stage_id" invisible="1"/>
<field name="categ_id" invisible="1"/>
<field name="priority" invisible="1"/>
<field name="type_id" invisible="1"/>
</tree>
</field>
</record>
<!-- CRM Claim Report Form View -->
<record id="view_report_crm_claim_form" model="ir.ui.view">
<field name="name">crm.claim.report.form</field>
<field name="model">crm.claim.report</field>
<field name="inherit_id" ref="view_crm_case_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="nbr" position="after">
<field name="delay_close"/>
<field name="amount_revenue"/>
<field name="amount_costs"/>
<field name="amount_revenue_prob"/>
<field name="probability"/>
</field>
</field>
</record>
<!-- CRM Claim Report Graph View -->
<record id="view_report_crm_claim_graph" model="ir.ui.view">
<field name="name">crm.claim.report.graph</field>
<field name="model">crm.claim.report</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<graph orientation="horizontal" string="Claims" type="bar">
<field name="state"/>
<field name="nbr" operator="+"/>
<field group="True" name="user_id"/>
</graph>
</field>
</record>
<!-- CRM Claim Report Search View -->
<record id="view_report_crm_claim_filter" model="ir.ui.view">
<field name="name">crm.claim.report.select</field>
<field name="model">crm.claim.report</field>
<field name="inherit_id" ref="view_crm_case_filter"/>
<field name="type">search</field>
<field name="arch" type="xml">
<data>
<xpath expr='//search[@string="Search"]/group[@string="Extended options..."]/filter[@string="Cancel"]' position='after'>
<group>
<separator orientation="vertical"/>
<field name="stage_id" widget="selection" domain="[('object_id.model', '=', 'crm.claim')]"/>
<field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'crm.claim')]"/>
<separator orientation="vertical"/>
<field name="priority" />
<field name="type_id" widget="selection" domain="[('object_id.model', '=', 'crm.claim')]"/>
</group>
</xpath>
<xpath
expr='//search[@string="Search"]/group[@string="Group By..."]/filter[@string="State"]'
position='after'>
<filter string="Stage" icon="terp-sale" domain="[]"
context="{'group_by':'stage_id'}" />
</xpath>
<xpath
expr='//search[@string="Search"]/group[@string="Group By..."]/filter[@string="Category"]'
position='after'>
<separator orientation="vertical"/>
<filter string="Priority" icon="terp-sale" domain="[]"
context="{'group_by':'priority'}" />
<filter string="Type" icon="terp-sale" domain="[]"
context="{'group_by':'type_id'}" />
</xpath>
</data>
</field>
</record>
<!-- CRM Claim Report Action -->
<record id="action_report_crm_claim" model="ir.actions.act_window">
<field name="name">Claims</field>
<field name="res_model">crm.claim.report</field>
<field name="view_type">form</field>
<field name="view_mode">tree,graph</field>
<field name="context">{"search_default_User":1,"search_default_This Month":1,'group_by_no_leaf':1,'group_by':[]}</field>
<field name="view_id" ref="view_report_crm_claim_tree"/>
<field name="search_view_id" ref="view_report_crm_claim_filter"/>
</record>
<record model="ir.actions.act_window.view" id="action_report_crm_claim_tree">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_report_crm_claim_tree"/>
<field name="act_window_id" ref="action_report_crm_claim"/>
</record>
<record model="ir.actions.act_window.view" id="action_report_crm_claim_graph">
<field name="sequence" eval="2"/>
<field name="view_mode">graph</field>
<field name="view_id" ref="view_report_crm_claim_graph"/>
<field name="act_window_id" ref="action_report_crm_claim"/>
</record>
<menuitem name="Claims" id="menu_report_crm_claim_tree"
groups="base.group_extended"
action="action_report_crm_claim" parent="base.next_id_64" />
</data>
</openerp>

View File

@ -1,115 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Fundraising by user and section Tree View-->
<record id="view_report_crm_fundraising_tree" model="ir.ui.view">
<field name="name">crm.fundraising.report.tree</field>
<field name="model">crm.fundraising.report</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Fundraising">
<field name="name" invisible="1"/>
<field name="month" invisible="1"/>
<field name="section_id" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="partner_id" invisible="1"/>
<field name="nbr" string='#Fundraising' sum="#Fundraising"/>
<field name="amount_revenue" sum='Est.Revenue'/>
<field name="probability" avg='Avg. Probability' widget="progressbar"/>
<field name="amount_revenue_prob" sum='Est. Rev*Prob.'/>
<field name="delay_close" avg='Avg Closing Delay'/>
<field name="state" invisible="1"/>
<field name="categ_id" invisible="1"/>
<field name="day" invisible="1"/>
</tree>
</field>
<!-- Fundraising by user and section Form View-->
</record>
<record id="view_report_crm_fundraising_form" model="ir.ui.view">
<field name="name">crm.fundraising.report.form</field>
<field name="model">crm.fundraising.report</field>
<field name="inherit_id" ref="view_crm_case_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="nbr" position="after">
<field name="delay_close"/>
<field name="amount_revenue"/>
<field name="amount_revenue_prob"/>
<field name="probability"/>
</field>
</field>
</record>
<!-- Fundraising by user and section Graph View-->
<record id="view_report_crm_fundraising_graph" model="ir.ui.view">
<field name="name">crm.fundraising.report.graph</field>
<field name="model">crm.fundraising.report</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<graph orientation="horizontal" string="Fundraising" type="bar">
<field name="state"/>
<field name="nbr" operator="+"/>
<field group="True" name="user_id"/>
</graph>
</field>
</record>
<!-- Fundraising by user and section Search View-->
<record id="view_report_crm_fundraising_filter" model="ir.ui.view">
<field name="name">crm.fundraising.report.select</field>
<field name="model">crm.fundraising.report</field>
<field name="inherit_id" ref="view_crm_case_filter"/>
<field name="type">search</field>
<field name="arch" type="xml">
<data>
<xpath expr='//search[@string="Search"]/group[@string="Extended options..."]/filter[@string="Cancel"]' position='after'>
<group>
<separator orientation="vertical"/>
<field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'crm.fundraising')]"/>
</group>
</xpath>
</data>
</field>
</record>
<!-- Fundraising by user and section Action -->
<record id="action_report_crm_fundraising" model="ir.actions.act_window">
<field name="name">Fundraising</field>
<field name="res_model">crm.fundraising.report</field>
<field name="view_type">form</field>
<field name="view_mode">tree,graph</field>
<field name="context">{"search_default_User":1,"search_default_This Month":1,'group_by_no_leaf':1,'group_by':[]}</field>
<field name="view_id" ref="view_report_crm_fundraising_tree"/>
<field name="search_view_id" ref="view_report_crm_fundraising_filter"/>
</record>
<record model="ir.actions.act_window.view" id="action_report_crm_fundrising_tree">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_report_crm_fundraising_tree"/>
<field name="act_window_id" ref="action_report_crm_fundraising"/>
</record>
<record model="ir.actions.act_window.view" id="action_report_crm_fundrising_graph">
<field name="sequence" eval="2"/>
<field name="view_mode">graph</field>
<field name="view_id" ref="view_report_crm_fundraising_graph"/>
<field name="act_window_id" ref="action_report_crm_fundraising"/>
</record>
<menuitem name="Fundraising"
action="action_report_crm_fundraising"
groups="base.group_extended"
id="menu_report_crm_fundraising_tree" parent="base.next_id_64" />
</data>
</openerp>

View File

@ -34,10 +34,62 @@ class crm_lead_report(osv.osv):
""" CRM Lead Report """
_name = "crm.lead.report"
_auto = False
_inherit = "crm.case.report"
_description = "CRM Lead Report"
def _get_data(self, cr, uid, ids, field_name, arg, context={}):
""" @param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case and section Datas IDs
@param context: A standard dictionary for contextual values """
res = {}
state_perc = 0.0
avg_ans = 0.0
for case in self.browse(cr, uid, ids, context):
if field_name != 'avg_answers':
state = field_name[5:]
cr.execute("select count(*) from crm_opportunity where \
section_id =%s and state='%s'"%(case.section_id.id, state))
state_cases = cr.fetchone()[0]
perc_state = (state_cases / float(case.nbr)) * 100
res[case.id] = perc_state
else:
model_name = self._name.split('report.')
if len(model_name) < 2:
res[case.id] = 0.0
else:
model_name = model_name[1]
cr.execute("select count(*) from crm_case_log l, ir_model m \
where l.model_id=m.id and m.model = '%s'" , model_name)
logs = cr.fetchone()[0]
avg_ans = logs / case.nbr
res[case.id] = avg_ans
return res
_columns = {
'name': fields.char('Year', size=64, required=False, readonly=True),
'user_id':fields.many2one('res.users', 'User', readonly=True),
'section_id':fields.many2one('crm.case.section', 'Section', readonly=True),
'nbr': fields.integer('# of Cases', readonly=True),
'state': fields.selection(AVAILABLE_STATES, 'State', size=16, readonly=True),
'avg_answers': fields.function(_get_data, string='Avg. Answers', method=True, type="integer"),
'perc_done': fields.function(_get_data, string='%Done', method=True, type="float"),
'perc_cancel': fields.function(_get_data, string='%Cancel', method=True, type="float"),
'month':fields.selection([('01', 'January'), ('02', 'February'), \
('03', 'March'), ('04', 'April'),\
('05', 'May'), ('06', 'June'), \
('07', 'July'), ('08', 'August'),\
('09', 'September'), ('10', 'October'),\
('11', 'November'), ('12', 'December')], 'Month', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'create_date': fields.datetime('Create Date', readonly=True),
'day': fields.char('Day', size=128, readonly=True),
'delay_close': fields.float('Delay to close',digits=(16,2),readonly=True, group_operator="avg",help="Number of Days to close the case"),
'categ_id': fields.many2one('crm.case.categ', 'Category',\
domain="[('section_id','=',section_id),\
@ -46,7 +98,12 @@ class crm_lead_report(osv.osv):
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.lead')]", readonly=True),
'partner_id': fields.many2one('res.partner', 'Partner' , readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'type':fields.selection([
('lead','Lead'),
('opportunity','Opportunity'),
],'Type', help="Type is used to separate Leads and Opportunities"),
}
def init(self, cr):
@ -66,6 +123,7 @@ class crm_lead_report(osv.osv):
c.state as state,
c.user_id,
c.stage_id,
c.type as type,
c.company_id,
c.section_id,
c.categ_id,
@ -79,7 +137,7 @@ class crm_lead_report(osv.osv):
from
crm_lead c
group by to_char(c.create_date, 'YYYY'), to_char(c.create_date, 'MM'),\
c.state, c.user_id,c.section_id,c.stage_id,categ_id,c.partner_id,c.company_id
c.state, c.user_id,c.section_id,c.stage_id,categ_id,c.partner_id,c.company_id, c.type
,c.create_date,to_char(c.create_date, 'YYYY-MM-DD')
)""")

View File

@ -17,11 +17,12 @@
<field name="company_id" invisible="1"/>
<field name="partner_id" invisible="1"/>
<field name="day" invisible="1"/>
<field name="nbr" string="#Leads" sum="#Leads"/>
<field name="nbr" string="#Leads" sum="#Leads" attrs="{'string': 'type'=='opportunity' and '#opp' or '#lead'}"/>
<field name="delay_close" sum='Avg Closing Delay'/>
<field name="state" invisible="1"/>
<field name="stage_id" invisible="1"/>
<field name="categ_id" invisible="1"/>
<field name="type" invisible="1"/>
</tree>
</field>
</record>
@ -31,13 +32,18 @@
<record id="view_report_crm_lead_form" model="ir.ui.view">
<field name="name">crm.lead.report.form</field>
<field name="model">crm.lead.report</field>
<field name="inherit_id" ref="view_crm_case_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="nbr" position="after">
<form string="Cases">
<field name="name" select="1"/>
<field name="month" select="1"/>
<field name="user_id" select="1"/>
<field name="section_id" select="1"/>
<field name="nbr" select="1"/>
<field name="delay_close"/>
<field name="stage_id"/>
</field>
<field name="state" select="1"/>
</form>
</field>
</record>
@ -57,28 +63,135 @@
</record>
<!-- Leads by user and section Search View -->
<record id="view_report_crm_lead_filter" model="ir.ui.view">
<record id="view_report_crm_lead_filter" model="ir.ui.view">
<field name="name">crm.lead.report.select</field>
<field name="model">crm.lead.report</field>
<field name="inherit_id" ref="view_crm_case_filter"/>
<field name="type">search</field>
<field name="arch" type="xml">
<data>
<xpath expr='//search[@string="Search"]/group[@string="Extended options..."]/filter[@string="Cancel"]' position='after'>
<group>
<separator orientation="vertical"/>
<field name="stage_id" widget="selection" domain="[('object_id.model', '=', 'crm.lead')]"/>
<field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'crm.lead')]"/>
</group>
</xpath>
<xpath
expr='//search[@string="Search"]/group[@string="Group By..."]/filter[@string="State"]'
position='after'>
<filter string="Stage" icon="terp-sale" domain="[]"
context="{'group_by':'stage_id'}" />
</xpath>
</data>
<search string="Search">
<group col="16" colspan="8">
<!-- <filter string="This Year" name="This Year" icon="terp-hr"
domain="[('name','=',time.localtime()[0])]"/>-->
<filter string="This Year" icon="terp-hr"
domain="[('create_date','&lt;=', time.strftime('%%Y-%%m-%%d')), ('create_date','&gt;',(datetime.date.today()-datetime.timedelta(days=365)).strftime('%%Y-%%m-%%d'))]"/>
<!-- <filter string="This Month" name="This Year" icon="terp-hr"
domain="[('month','=',time.strftime('%%m'))]" />-->
<filter string="This Month" icon="terp-hr" name="This Month"
domain="[('create_date','&lt;=', time.strftime('%%Y-%%m-%%d')), ('create_date','&gt;',(datetime.date.today()-datetime.timedelta(days=30)).strftime('%%Y-%%m-%%d'))]"/>
<filter icon="gtk-media-rewind" string="7 Days" separator="1"
domain="[('create_date','&lt;=', time.strftime('%%Y-%%m-%%d')), ('create_date','&gt;',(datetime.date.today()-datetime.timedelta(days=7)).strftime('%%Y-%%m-%%d'))]"/>
<separator orientation="vertical" />
<filter icon="terp-hr"
string="Draft"
domain="[('state','=','draft')]"/>
<filter icon="terp-hr"
string="Open"
domain="[('state','=','open')]"/>
<filter icon="terp-hr"
string="Pending"
domain="[('state','=','pending')]"/>
<separator orientation="vertical" />
<field name="section_id"
default="context.get('section_id', False)"
widget="selection"
context="{'invisible_section': False}">
<filter icon="terp-crm"
context="{'invisible_section': False}"
domain="[('section_id.user_id','=',uid)]"
help="My section" />
</field>
<field name="company_id" widget="selection">
<filter icon="terp-crm"
context="{'invisible_section': False}"
domain="[('section_id.user_id.company_id','=',uid)]"
help="My company"/>
</field>
<field name="user_id" select="1" widget="selection">
<filter icon="terp-crm" string="My Case" help="My Case" domain="[('user_id','=',uid)]" />
</field>
</group>
<newline/>
<group expand="1" string="Extended options..." colspan="10" col="12">
<filter icon="terp-sale"
string="Done"
domain="[('state','=','done')]"/>
<filter icon="terp-sale"
string="Cancel"
domain="[('state','=','cancel')]"/>
<group>
<separator orientation="vertical"/>
<field name="stage_id" widget="selection" domain="[('object_id.model', '=', 'crm.lead')]"/>
<field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'crm.lead')]"/>
</group>
</group>
<newline/>
<group expand="1" string="Group By..." colspan="4" col="8">
<filter string="User" name="User" icon="terp-sale"
domain="[]" context="{'group_by':'user_id'}" />
<filter string="Company" icon="terp-sale"
domain="[]"
context="{'group_by':'company_id'}" />
<filter string="Section" icon="terp-sale"
domain="[]"
context="{'group_by':'section_id'}" />
<separator orientation="vertical" />
<filter string="State" icon="terp-sale"
domain="[]" context="{'group_by':'state'}" />
<filter string="Stage" icon="terp-sale" domain="[]"
context="{'group_by':'stage_id'}" />
<filter string="Category" icon="terp-sale"
domain="[]" context="{'group_by':'categ_id'}" />
<separator orientation="vertical" />
<filter string="Day" icon="terp-sale"
domain="[]" context="{'group_by':'day'}"/>
<filter string="Month" icon="terp-sale"
domain="[]" context="{'group_by':'month'}" />
<filter string="Year" icon="terp-sale"
domain="[]" context="{'group_by':'name'}" />
</group>
</search>
</field>
</record>
<!-- Opportunity tree view -->
<record id="view_report_crm_opportunity_tree" model="ir.ui.view">
<field name="name">crm.lead.report.tree</field>
<field name="model">crm.lead.report</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Opportunities">
<field name="name" invisible="1"/>
<field name="month" invisible="1"/>
<field name="section_id" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="partner_id" invisible="1"/>
<field name="day" invisible="1"/>
<field name="nbr" string="#Opportunities" sum="#Opportunities"/>
<field name="delay_close" sum='Avg Closing Delay'/>
<field name="state" invisible="1"/>
<field name="stage_id" invisible="1"/>
<field name="categ_id" invisible="1"/>
<field name="type" invisible="1"/>
</tree>
</field>
</record>
@ -90,8 +203,8 @@
<field name="view_type">form</field>
<field name="context">{"search_default_User":1,"search_default_This Month":1,'group_by_no_leaf':1,'group_by':[]}</field>
<field name="view_mode">tree,graph</field>
<field name="domain">[('type', '=', 'lead')]</field>
<field name="search_view_id" ref="view_report_crm_lead_filter"/>
</record>
<record model="ir.actions.act_window.view" id="action_report_crm_lead_tree">
@ -107,10 +220,38 @@
<field name="view_id" ref="view_report_crm_lead_graph"/>
<field name="act_window_id" ref="action_report_crm_lead"/>
</record>
<record id="action_report_crm_opportunity" model="ir.actions.act_window">
<field name="name">Opportunities</field>
<field name="res_model">crm.lead.report</field>
<field name="view_type">form</field>
<field name="context">{"search_default_User":1,"search_default_This Month":1,'group_by_no_leaf':1,'group_by':[]}</field>
<field name="view_mode">tree,graph</field>
<field name="domain">[('type', '=', 'opportunity')]</field>
<field name="search_view_id" ref="view_report_crm_lead_filter"/>
</record>
<record model="ir.actions.act_window.view" id="action_report_crm_opportunity_tree">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_report_crm_opportunity_tree"/>
<field name="act_window_id" ref="action_report_crm_opportunity"/>
</record>
<record model="ir.actions.act_window.view" id="action_report_crm_opportunity_graph">
<field name="sequence" eval="2"/>
<field name="view_mode">graph</field>
<field name="view_id" ref="view_report_crm_lead_graph"/>
<field name="act_window_id" ref="action_report_crm_opportunity"/>
</record>
<menuitem name="Leads" id="menu_report_crm_leads_tree"
groups="base.group_extended"
parent="base.next_id_64" action="action_report_crm_lead" />
<menuitem name="Opportunities" id="menu_report_crm_opportunities_tree"
groups="base.group_extended"
parent="base.next_id_64" action="action_report_crm_opportunity" />
</data>
</openerp>

View File

@ -1,100 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import fields,osv
import tools
AVAILABLE_STATES = [
('draft','Draft'),
('open','Open'),
('cancel', 'Cancelled'),
('done', 'Closed'),
('pending','Pending')
]
class crm_opportunity_report(osv.osv):
""" CRM Opportunity Report """
_name = "crm.opportunity.report"
_auto = False
_inherit = "crm.case.report"
_description = "CRM Opportunity Report"
_columns = {
'probability': fields.float('Avg. Probability', readonly=True,group_operator='avg'),
'amount_revenue': fields.float('Est.Revenue', readonly=True),
'amount_revenue_prob': fields.float('Est. Rev*Prob.', readonly=True),
'delay_close': fields.float('Delay to close',digits=(16,2),readonly=True, group_operator="avg",help="Number of Days to close the case"),
'categ_id': fields.many2one('crm.case.categ', 'Category',\
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.opportunity')]", readonly=True),
'stage_id':fields.many2one('crm.case.stage', 'Stage',\
domain="[('section_id','=',section_id),\
('object_id.model', '=', 'crm.opportunity')]", readonly=True),
'partner_id': fields.many2one('res.partner', 'Partner', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'user_id':fields.many2one('res.users', 'User', readonly=True),
}
def init(self, cr):
""" Display Est.Revenue , Average Probability ,Est.Revenue Probability
@param cr: the current row, from the database cursor
"""
tools.drop_view_if_exists(cr, 'crm_opportunity_report')
cr.execute("""
create or replace view crm_opportunity_report as (
select
min(c.id) as id,
to_char(c.create_date, 'YYYY') as name,
to_char(c.create_date, 'MM') as month,
to_char(c.create_date, 'YYYY-MM-DD') as day,
c.state as state,
c.user_id,
c.section_id,
c.categ_id,
c.stage_id,
c.partner_id,
c.company_id,
count(*) as nbr,
0 as avg_answers,
0.0 as perc_done,
0.0 as perc_cancel,
date_trunc('day',c.create_date) as create_date,
sum(planned_revenue) as amount_revenue,
sum((planned_revenue*probability)/100.0)::decimal(16,2) as amount_revenue_prob,
avg(probability)::decimal(16,2) as probability,
avg(extract('epoch' from (c.date_closed-c.create_date)))/(3600*24) as delay_close
from
crm_opportunity c
group by
to_char(c.create_date, 'YYYY'),
to_char(c.create_date, 'MM'),
to_char(c.create_date, 'YYYY-MM-DD'),
c.state,
c.user_id,
c.section_id,
c.stage_id,
c.categ_id,
c.partner_id,
company_id,
create_date
)""")
crm_opportunity_report()

View File

@ -1,122 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Opportunities by user and section Tree View -->
<record id="view_report_crm_opportunity_tree" model="ir.ui.view">
<field name="name">crm.opportunity.report.tree</field>
<field name="model">crm.opportunity.report</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Opportunities">
<field name="name" invisible="1"/>
<field name="month" invisible="1"/>
<field name="section_id" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="partner_id" invisible="1"/>
<field name="nbr" string="#Opportunities" sum="#Opportunities"/>
<field name="amount_revenue" sum="Total Revenue"/>
<field name="probability" widget="progressbar" avg="Probability"/>
<field name="amount_revenue_prob" sum="Est. Rev*Prob"/>
<field name="delay_close" avg="Avg Closing Delay"/>
<field name="state" invisible="1"/>
<field name="stage_id" invisible="1"/>
<field name="categ_id" invisible="1"/>
<field name="day" invisible="1"/>
</tree>
</field>
</record>
<!-- Opportunities by user and section Form View -->
<record id="view_report_crm_opportunity_form" model="ir.ui.view">
<field name="name">crm.opportunity.report.form</field>
<field name="model">crm.opportunity.report</field>
<field name="inherit_id" ref="view_crm_case_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="nbr" position="after">
<field name="delay_close"/>
<field name="amount_revenue"/>
<field name="amount_costs"/>
<field name="amount_revenue_prob"/>
<field name="probability"/>
</field>
</field>
</record>
<!-- Opportunities by user and section Graph View -->
<record id="view_report_crm_opportunity_graph" model="ir.ui.view">
<field name="name">crm.opportunity.report.graph</field>
<field name="model">crm.opportunity.report</field>
<field name="type">graph</field>
<field name="arch" type="xml">
<graph orientation="horizontal" string="Opportunity" type="bar">
<field name="state"/>
<field name="nbr" operator="+"/>
<field group="True" name="user_id"/>
</graph>
</field>
</record>
<!-- Opportunities by user and section Search View -->
<record id="view_report_crm_opportunity_filter" model="ir.ui.view">
<field name="name">crm.opportunity.report.select</field>
<field name="model">crm.opportunity.report</field>
<field name="inherit_id" ref="view_crm_case_filter"/>
<field name="type">search</field>
<field name="arch" type="xml">
<data>
<xpath expr='//search[@string="Search"]/group[@string="Extended options..."]/filter[@string="Cancel"]' position='after'>
<group>
<separator orientation="vertical"/>
<field name="stage_id" widget="selection" domain="[('object_id.model', '=', 'crm.opportunity')]"/>
<field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'crm.opportunity')]"/>
</group>
</xpath>
<xpath
expr='//search[@string="Search"]/group[@string="Group By..."]/filter[@string="State"]'
position='after'>
<filter string="Stage" icon="terp-sale" domain="[]"
context="{'group_by':'stage_id'}" />
<separator orientation="vertical" />
<filter string="Partner" icon="terp-partner"
domain="[]" context="{'group_by':'partner_id'}" />
</xpath>
</data>
</field>
</record>
<!-- Opportunities by user and section Action -->
<record id="action_report_crm_opportunity" model="ir.actions.act_window">
<field name="name">Opportunities</field>
<field name="res_model">crm.opportunity.report</field>
<field name="view_type">form</field>
<field name="view_mode">tree,graph</field>
<field name="context">{"search_default_User":1,"search_default_This Month":1,'group_by_no_leaf':1,'group_by':[]}</field>
<field name="search_view_id" ref="view_report_crm_opportunity_filter"/>
</record>
<record model="ir.actions.act_window.view" id="action_report_crm_opportunity_tree">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_report_crm_opportunity_tree"/>
<field name="act_window_id" ref="action_report_crm_opportunity"/>
</record>
<record model="ir.actions.act_window.view" id="action_report_crm_opportunity_graph">
<field name="sequence" eval="2"/>
<field name="view_mode">graph</field>
<field name="view_id" ref="view_report_crm_opportunity_graph"/>
<field name="act_window_id" ref="action_report_crm_opportunity"/>
</record>
<menuitem name="Opportunities"
action="action_report_crm_opportunity"
id="menu_report_crm_opportunity_tree" parent="base.next_id_64" />
</data>
</openerp>

View File

@ -22,15 +22,76 @@
from osv import fields,osv
import tools
AVAILABLE_STATES = [
('draft','Draft'),
('open','Open'),
('cancel', 'Cancelled'),
('done', 'Closed'),
('pending','Pending')
]
class crm_phonecall_report(osv.osv):
""" Phone calls by user and section """
_name = "crm.phonecall.report"
_description = "Phone calls by user and section"
_auto = False
_inherit = "crm.case.report"
def _get_data(self, cr, uid, ids, field_name, arg, context={}):
""" @param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of case and section Datas IDs
@param context: A standard dictionary for contextual values """
res = {}
state_perc = 0.0
avg_ans = 0.0
for case in self.browse(cr, uid, ids, context):
if field_name != 'avg_answers':
state = field_name[5:]
cr.execute("select count(*) from crm_opportunity where \
section_id =%s and state='%s'"%(case.section_id.id, state))
state_cases = cr.fetchone()[0]
perc_state = (state_cases / float(case.nbr)) * 100
res[case.id] = perc_state
else:
model_name = self._name.split('report.')
if len(model_name) < 2:
res[case.id] = 0.0
else:
model_name = model_name[1]
cr.execute("select count(*) from crm_case_log l, ir_model m \
where l.model_id=m.id and m.model = '%s'" , model_name)
logs = cr.fetchone()[0]
avg_ans = logs / case.nbr
res[case.id] = avg_ans
return res
_columns = {
'name': fields.char('Year', size=64, required=False, readonly=True),
'user_id':fields.many2one('res.users', 'User', readonly=True),
'section_id':fields.many2one('crm.case.section', 'Section', readonly=True),
'nbr': fields.integer('# of Cases', readonly=True),
'state': fields.selection(AVAILABLE_STATES, 'State', size=16, readonly=True),
'avg_answers': fields.function(_get_data, string='Avg. Answers', method=True, type="integer"),
'perc_done': fields.function(_get_data, string='%Done', method=True, type="float"),
'perc_cancel': fields.function(_get_data, string='%Cancel', method=True, type="float"),
'month':fields.selection([('01', 'January'), ('02', 'February'), \
('03', 'March'), ('04', 'April'),\
('05', 'May'), ('06', 'June'), \
('07', 'July'), ('08', 'August'),\
('09', 'September'), ('10', 'October'),\
('11', 'November'), ('12', 'December')], 'Month', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'create_date': fields.datetime('Create Date', readonly=True),
'day': fields.char('Day', size=128, readonly=True),
'delay_close': fields.float('Delay to close', digits=(16,2),readonly=True, group_operator="avg",help="Number of Days to close the case"),
'categ_id': fields.many2one('crm.case.categ', 'Category', \
domain="[('section_id','=',section_id),\

View File

@ -30,15 +30,20 @@
<record id="view_report_crm_phonecall_form" model="ir.ui.view">
<field name="name">crm.phonecall.report.form</field>
<field name="model">crm.phonecall.report</field>
<field name="inherit_id" ref="view_crm_case_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="nbr" position="after">
<field name="delay_close"/>
<form string="Cases">
<field name="name" select="1"/>
<field name="month" select="1"/>
<field name="user_id" select="1"/>
<field name="section_id" select="1"/>
<field name="nbr" select="1"/>
<field name="delay_close"/>
<field name="amount_revenue"/>
<field name="amount_revenue_prob"/>
<field name="probability"/>
</field>
<field name="state" select="1"/>
</form>
</field>
</record>
@ -58,21 +63,108 @@
</record>
<!-- Phone calls by user and section Search View -->
<record id="view_report_crm_phonecall_filter" model="ir.ui.view">
<record id="view_report_crm_phonecall_filter" model="ir.ui.view">
<field name="name">crm.phonecall.report.select</field>
<field name="model">crm.phonecall.report</field>
<field name="inherit_id" ref="view_crm_case_filter"/>
<field name="type">search</field>
<field name="arch" type="xml">
<data>
<xpath expr='//search[@string="Search"]/group[@string="Extended options..."]/filter[@string="Cancel"]' position='after'>
<group>
<separator orientation="vertical"/>
<field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'crm.phonecall')]"/>
</group>
</xpath>
</data>
<search string="Search">
<group col="16" colspan="8">
<!-- <filter string="This Year" name="This Year" icon="terp-hr"
domain="[('name','=',time.localtime()[0])]"/>-->
<filter string="This Year" icon="terp-hr"
domain="[('create_date','&lt;=', time.strftime('%%Y-%%m-%%d')), ('create_date','&gt;',(datetime.date.today()-datetime.timedelta(days=365)).strftime('%%Y-%%m-%%d'))]"/>
<!-- <filter string="This Month" name="This Year" icon="terp-hr"
domain="[('month','=',time.strftime('%%m'))]" />-->
<filter string="This Month" icon="terp-hr" name="This Month"
domain="[('create_date','&lt;=', time.strftime('%%Y-%%m-%%d')), ('create_date','&gt;',(datetime.date.today()-datetime.timedelta(days=30)).strftime('%%Y-%%m-%%d'))]"/>
<filter icon="gtk-media-rewind" string="7 Days" separator="1"
domain="[('create_date','&lt;=', time.strftime('%%Y-%%m-%%d')), ('create_date','&gt;',(datetime.date.today()-datetime.timedelta(days=7)).strftime('%%Y-%%m-%%d'))]"/>
<separator orientation="vertical" />
<filter icon="terp-hr"
string="Draft"
domain="[('state','=','draft')]"/>
<filter icon="terp-hr"
string="Open"
domain="[('state','=','open')]"/>
<filter icon="terp-hr"
string="Pending"
domain="[('state','=','pending')]"/>
<separator orientation="vertical" />
<field name="section_id"
default="context.get('section_id', False)"
widget="selection"
context="{'invisible_section': False}">
<filter icon="terp-crm"
context="{'invisible_section': False}"
domain="[('section_id.user_id','=',uid)]"
help="My section" />
</field>
<field name="company_id" widget="selection">
<filter icon="terp-crm"
context="{'invisible_section': False}"
domain="[('section_id.user_id.company_id','=',uid)]"
help="My company"/>
</field>
<field name="user_id" select="1" widget="selection">
<filter icon="terp-crm" string="My Case" help="My Case" domain="[('user_id','=',uid)]" />
</field>
</group>
<newline/>
<group expand="1" string="Extended options..." colspan="10" col="12">
<filter icon="terp-sale"
string="Done"
domain="[('state','=','done')]"/>
<filter icon="terp-sale"
string="Cancel"
domain="[('state','=','cancel')]"/>
<group>
<separator orientation="vertical"/>
<field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'crm.phonecall')]"/>
</group>
</group>
<newline/>
<group expand="1" string="Group By..." colspan="4" col="8">
<filter string="User" name="User" icon="terp-sale"
domain="[]" context="{'group_by':'user_id'}" />
<filter string="Company" icon="terp-sale"
domain="[]"
context="{'group_by':'company_id'}" />
<filter string="Section" icon="terp-sale"
domain="[]"
context="{'group_by':'section_id'}" />
<separator orientation="vertical" />
<filter string="State" icon="terp-sale"
domain="[]" context="{'group_by':'state'}" />
<filter string="Category" icon="terp-sale"
domain="[]" context="{'group_by':'categ_id'}" />
<separator orientation="vertical" />
<filter string="Day" icon="terp-sale"
domain="[]" context="{'group_by':'day'}"/>
<filter string="Month" icon="terp-sale"
domain="[]" context="{'group_by':'month'}" />
<filter string="Year" icon="terp-sale"
domain="[]" context="{'group_by':'name'}" />
</group>
</search>
</field>
</record>

View File

@ -0,0 +1,103 @@
<?php
include("xmlrpc.inc");
$user = "";
$password = "";
$db = "";
$serverUri = "";
$client = new xmlrpc_client($serverUri.'common');
$msg = new xmlrpcmsg('login');
$msg->addParam(new xmlrpcval($db, "string"));
$msg->addParam(new xmlrpcval($user, "string"));
$msg->addParam(new xmlrpcval($pass, "string"));
$res = &$client->send($msg);
if(!$res->faultCode()){
$id = $res->value()->scalarval();
$client = new xmlrpc_client($serverUri.'object');
$key = array(new xmlrpcval(array(new xmlrpcval("id", "string"),
new xmlrpcval(">", "string"),
new xmlrpcval(0, "int")),"array"),);
$msg = new xmlrpcmsg('execute');
$msg->addParam(new xmlrpcval($db, "string"));
$msg->addParam(new xmlrpcval($id, "int"));
$msg->addParam(new xmlrpcval($pass, "string"));
$msg->addParam(new xmlrpcval("res.country","string"));
$msg->addParam(new xmlrpcval("search", "string"));
$msg->addParam(new xmlrpcval($key, "array"));
$msg->addParam(new xmlrpcval(0, "int"));
$msg->addParam(new xmlrpcval(0, "int"));
$msg->addParam(new xmlrpcval("id ASC", "string"));
$res = &$client->send($msg);
if(!$res->faultCode())
{
$val = $res->value()->scalarval();
$ides = array();
for ($i=0 ; $i<count($val); $i++)
{
array_push($ides, new xmlrpcval($val[$i]->scalarval(), "int"));
}
$client = new xmlrpc_client($serverUri.'object');
$fields = array(new xmlrpcval("code", "string"), new xmlrpcval("name", "string"));
$msg = new xmlrpcmsg('execute');
$msg->addParam(new xmlrpcval($db, "string"));
$msg->addParam(new xmlrpcval($id, "int"));
$msg->addParam(new xmlrpcval($pass, "string"));
$msg->addParam(new xmlrpcval("res.country","string"));
$msg->addParam(new xmlrpcval("read", "string"));
$msg->addParam(new xmlrpcval($ides, "array"));
$msg->addParam(new xmlrpcval($fields, "array"));
$res = &$client->send($msg);
if (!$res->faultCode())
{
$val = $res->value()->scalarval();
$select ='<select class="inputbox required" name="country" id="country" >
<option value="" selected="selected"> -- Select an Option -- </option>';
for ($i=0; $i<count($val);$i++)
{
$field = $val[$i]->scalarval();
$select .= '<option value="'.$field['code']->scalarval().'">'.$field['name']->scalarval().'</option>';
}
$select .= '</select>';
echo $select;
}
else
{
echo "Country not getting";
}
}
else
{
echo "Country list id empty";
}
}
else
{
echo "connection not establish";
}
exit;
?>

View File

@ -0,0 +1,201 @@
<?php
include("xmlrpc.inc");
class Contact
{
private $subject = '', $to = '';
function __construct($to, $sub)
{
$this->to = $to;
$this->subject = $sub;
}
function mailTo($post)
{
if (!empty($post))
{
if (empty($this->to))
{
$this->to = 'sales@tinyerp.com';
}
if (empty($this->subject))
{
$this->subject = 'Contact form information';
}
$body = '<table width="90%" border="0" cellspacing="2" cellpadding="2">
<tr>
<td width="25%"></td>
<td width="2%"></td>
<td></td>
</tr>';
foreach ($post as $key => $value)
{
$body .= "<tr>
<td align='right'>$key</td>
<td align='center'><b>:</b></td>
<td align='left'>$value</td>
</tr>";
}
$body .= '</table>';
$header = 'MEME-version : 1.0'."\r\n";
$header .= 'Content-type: text/html; charset=iso-8859-1'."\r\n";
$header .= 'From: '.$post['name'].' <'.$post['email'].'>'."\r\n";
$header .= 'Reply-To: '.$post['name'].' <'.$post['email'].'>'."\r\n";
$header .= 'Return-Path: '.$post['name'].' <'.$post['email'].'>'."\r\n";
if (mail($this->to, $this->subject, $body, $header))
{
echo "<br /><h3>Thank You, Your information has been successfully submitted.</h3><br /><p><b>We will be in touch very soon.</b></p>";
}
else
{
echo "<br /><h4>Sorry, Due to some problem Your information has not been submitted.</h4>";
}
}
}
function xmlCallTo($usr, $password, $database, $server, $post)
{
$user = $usr;
$pass = $password;
$db = $database;
$server_url = $server; //'http://localhost:8069/xmlrpc/'
$client = new xmlrpc_client($server_url.'common');
$msg = new xmlrpcmsg('login');
$msg->addParam(new xmlrpcval($db, "string"));
$msg->addParam(new xmlrpcval($user, "string"));
$msg->addParam(new xmlrpcval($pass, "string"));
$res = &$client->send($msg);
if(!$res->faultCode()){
$val = $res->value();
$id = $val->scalarval();
if (empty($id)){
echo "Connection error = ";
exit;
}
else
{
$client2 = new xmlrpc_client($server_url.'object');
$key = array(new xmlrpcval(array(new xmlrpcval("code", "string"), new xmlrpcval("=", "string"), new xmlrpcval($post['country'], "string")),"array"),);
$msg = new xmlrpcmsg('execute');
$msg->addParam(new xmlrpcval($db, "string"));
$msg->addParam(new xmlrpcval($id, "int"));
$msg->addParam(new xmlrpcval($pass, "string"));
$msg->addParam(new xmlrpcval("res.country","string"));
$msg->addParam(new xmlrpcval("search", "string"));
$msg->addParam(new xmlrpcval($key, "array"));
$msg->addParam(new xmlrpcval(0, "int"));
$msg->addParam(new xmlrpcval(1, "int"));
$res = &$client2->send($msg);
$val = $res->value()->scalarval();
$countryId = $val[0]->scalarval();
$val = array ("name" => new xmlrpcval($post['company'],"string"),
"title" => new xmlrpcval($post['salutation'], "string"),
"email_from" => new xmlrpcval($post['email'], "string"),
"phone" => new xmlrpcval($post['phone'], "string"),
"partner_name" => new xmlrpcval($post['name'], "string"),
"function_name" => new xmlrpcval($post["jobtitle"], "string"),
"zip" => new xmlrpcval($post['zip'], "string"),
"stage_id" => new xmlrpcval(6, "int"),
"city" => new xmlrpcval($post['city'], "string"),
"country_id" => new xmlrpcval($countryId, "int"),
"state" => new xmlrpcval("draft", "string"),
"user_id" => new xmlrpcval(false, "boolean"),
"description" => new xmlrpcval("No.of Employees: ".$post['employees']."\nState: ".$post['state']."\nIndustry: ".$post['industry']."\nReasons: ".$post['reasons']."\nAbout: ".$post['about']."\nQuestions: ".stripslashes($post['questions'])."\nUpdate: ".$post['update'], "string")
);
$msg = new xmlrpcmsg('execute');
$msg->addParam(new xmlrpcval($db, "string"));
$msg->addParam(new xmlrpcval($id, "int"));
$msg->addParam(new xmlrpcval($pass, "string"));
$msg->addParam(new xmlrpcval("crm.lead", "string"));
$msg->addParam(new xmlrpcval("create", "string"));
$msg->addParam(new xmlrpcval($val, "struct"));
$res2 = &$client2->send($msg);
if(!$res2->faultCode())
{
$readVal = $res2->value()->scalarval();
if (!empty($readVal))
{
$val = array ( "description" => new xmlrpcval("About: ".$post['about']."\nQuestions: ".$post['questions']),
"model_id" => new xmlrpcval(276, "int"),
"res_id" => new xmlrpcval($readVal,"int"),
"email_from" => new xmlrpcval($post['email'], "string"),
"email_to" => new xmlrpcval("sales@openerp.com", "string")
);
$msg = new xmlrpcmsg('execute');
$msg->addParam(new xmlrpcval($db, "string"));
$msg->addParam(new xmlrpcval($id, "int"));
$msg->addParam(new xmlrpcval($pass, "string"));
$msg->addParam(new xmlrpcval("crm.case.history", "string"));
$msg->addParam(new xmlrpcval("create", "string"));
$msg->addParam(new xmlrpcval($val, "struct"));
$res2 = &$client2->send($msg);
//echo "<br />Successfully created lead";
echo "<br /><h3>Thank You for your interest in openerp, we'll respond to your request shortly.</h3><br />";
if(strstr($post["about"],"Book")) {
echo '<script>window.location="http://www.openerp.com/index.php?option=com_content&amp;id=54"</script>';
}
}
else
{
echo "<br />Lead is not created";
}
}
else
{
echo "<br />Problem in message sending for create lead";
}
}
}
else
{
echo "<br />Connection not established";
}
}
}
$arrData = array();
$arrData = array_merge($arrData, (array)$_POST);
$cnt = new Contact('sales5@openerp.com', 'Country: '.$arrData['country']. ' About: ' .$arrData['about']);
/* This function use for sending mail on perticular mail account */
/*$cnt->mailTo($arrData); */
/* This function use ceating lead in crm of opener erp database */
$cnt->xmlCallTo('<username>', '<password>', '<db>', '<server>', $arrData);
exit;
?>

3600
addons/crm/scripts/php/xmlrpc.inc Executable file

File diff suppressed because it is too large Load Diff

View File

@ -3,43 +3,30 @@
"access_crm_segmentation_line","crm.segmentation.line","model_crm_segmentation_line","crm.group_crm_manager",1,1,1,1
"access_crm_case_section","crm.case.section","model_crm_case_section","crm.group_crm_user",1,0,0,0
"access_crm_case_categ","crm.case.categ","model_crm_case_categ","crm.group_crm_user",1,0,0,0
"access_crm_case_manger","crm.case manager","model_crm_case","crm.group_crm_user",1,1,1,1
"access_crm_case","crm.case","model_crm_case","crm.group_crm_manager",1,1,1,1
"access_crm_claim","crm.claim","model_crm_claim","crm.group_crm_manager",1,1,1,1
"access_crm_fundraising","crm.fundraising","model_crm_fundraising","crm.group_crm_manager",1,1,1,1
"access_crm_helpdesk","crm.helpdesk","model_crm_helpdesk","crm.group_crm_manager",1,1,1,1
"access_crm_meeting","crm.meeting","model_crm_meeting","crm.group_crm_manager",1,1,1,1
"access_crm_lead","crm.lead","model_crm_lead","crm.group_crm_manager",1,1,1,1
"access_crm_opportunity","crm.opportunity","model_crm_opportunity","crm.group_crm_manager",1,1,1,1
"access_crm_phonecall","crm.phonecall","model_crm_phonecall","crm.group_crm_manager",1,1,1,1
"access_crm_case_log","crm.case.log","model_crm_case_log","crm.group_crm_user",1,1,1,1
"access_crm_case_history","crm.case.history","model_crm_case_history","crm.group_crm_user",1,1,1,1
"access_crm_case_section_manager","crm.case.section.manager","model_crm_case_section","crm.group_crm_manager",1,1,1,1
"o","crm.case.section.manager","model_crm_case_section","crm.group_crm_manager",1,1,1,1
"access_crm_case_categ_manager","crm.case.categ.manager","model_crm_case_categ","crm.group_crm_manager",1,1,1,1
"access_crm_case_log_manager","crm.case.log manager","model_crm_case_log","crm.group_crm_manager",1,1,1,1
"access_crm_case_history_manager","crm.case.history manager","model_crm_case_history","crm.group_crm_manager",1,1,1,1
"access_crm_email_add_cc_manager","crm.email.add.cc","model_crm_email_add_cc","crm.group_crm_manager",1,1,1,1
"access_crm_email_add_cc_user","crm.email.add.cc","model_crm_email_add_cc","crm.group_crm_user",1,0,0,0
"access_crm_case_stage","crm.case.stage","model_crm_case_stage","crm.group_crm_user",1,0,0,0
"access_crm_case_stage_manager","crm.case.stage","model_crm_case_stage","crm.group_crm_manager",1,1,1,1
"access_crm_case_resource_type_user","crm_case_resource_type user","model_crm_case_resource_type","crm.group_crm_user",1,0,0,0
"access_crm_case_resource_type_manager","crm_case_resource_type manager","model_crm_case_resource_type","crm.group_crm_manager",1,1,1,1
"access_crm_case_report_user","crm.case.report","model_crm_case_report","crm.group_crm_user",1,0,0,0
"access_crm_case_report_manager","crm.case.report manager","model_crm_case_report","crm.group_crm_manager",1,1,1,1
"access_crm_claim_report_user","crm.claim.report","model_crm_claim_report","crm.group_crm_user",1,0,0,0
"access_crm_fundraising_report_user","crm.fundraising.report","model_crm_fundraising_report","crm.group_crm_user",1,0,0,0
"access_crm_lead_report_user","crm.lead.report","model_crm_lead_report","crm.group_crm_user",1,0,0,0
"access_crm_phonecall_report_user","crm.phonecall.report","model_crm_phonecall_report","crm.group_crm_user",1,0,0,0
"access_crm_opportunity_report_user","crm.opportunity.report","model_crm_opportunity_report","crm.group_crm_user",1,0,0,0
"access_report_crm_case_service_dashboard","report.crm.case.service.dashboard","model_report_crm_case_service_dashboard","crm.group_crm_user",1,0,0,0
"access_crm_lead2partner","crm.lead2partner","model_crm_lead2partner","crm.group_crm_user",1,1,1,1
"access_crm_lead2opportunity","crm.lead2opportunity","model_crm_lead2opportunity","crm.group_crm_user",1,1,1,1
"access_crm_opportunity2phonecall","crm.opportunity2phonecall","model_crm_opportunity2phonecall","crm.group_crm_user",1,1,1,1
"access_crm_phonecall2phonecall","crm.phonecall2phonecall","model_crm_phonecall2phonecall","crm.group_crm_user",1,1,1,1
"access_crm_phonecall2partner","crm.phonecall2partner","model_crm_phonecall2partner","crm.group_crm_user",1,1,1,1
"access_crm_phonecall2opportunity","crm.phonecall2opportunity","model_crm_phonecall2opportunity","crm.group_crm_user",1,1,1,1
"access_report_crm_helpdesk","report.crm.helpdesk","model_crm_helpdesk_report","crm.group_crm_user",1,1,1,1
"access_crm_send_mail","crm.send.mail","model_crm_send_mail","crm.group_crm_user",1,1,1,1
"access_crm_send_mail_attachment","crm.send.mail.attachment","model_crm_send_mail_attachment","crm.group_crm_user",1,1,1,1
"access_crm_partner2opportunity","crm.partner2opportunity","model_crm_partner2opportunity","crm.group_crm_user",1,1,1,1
"access_crm_lead2opportunity_partner","crm.lead2opportunity.partner","model_crm_lead2opportunity_partner","crm.group_crm_user",1,1,1,1
"access_crm_installer","crm.installer.rule","model_crm_installer","crm.group_crm_user",1,1,1,1
"access_crm_lead_forward_to_partner","crm.lead.forward.to.partner","model_crm_lead_forward_to_partner","crm.group_crm_user",2,2,2,2

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
3 access_crm_segmentation_line crm.segmentation.line model_crm_segmentation_line crm.group_crm_manager 1 1 1 1
4 access_crm_case_section crm.case.section model_crm_case_section crm.group_crm_user 1 0 0 0
5 access_crm_case_categ crm.case.categ model_crm_case_categ crm.group_crm_user 1 0 0 0
access_crm_case_manger crm.case manager model_crm_case crm.group_crm_user 1 1 1 1
access_crm_case crm.case model_crm_case crm.group_crm_manager 1 1 1 1
access_crm_claim crm.claim model_crm_claim crm.group_crm_manager 1 1 1 1
access_crm_fundraising crm.fundraising model_crm_fundraising crm.group_crm_manager 1 1 1 1
access_crm_helpdesk crm.helpdesk model_crm_helpdesk crm.group_crm_manager 1 1 1 1
6 access_crm_meeting crm.meeting model_crm_meeting crm.group_crm_manager 1 1 1 1
7 access_crm_lead crm.lead model_crm_lead crm.group_crm_manager 1 1 1 1
access_crm_opportunity crm.opportunity model_crm_opportunity crm.group_crm_manager 1 1 1 1
8 access_crm_phonecall crm.phonecall model_crm_phonecall crm.group_crm_manager 1 1 1 1
9 access_crm_case_log crm.case.log model_crm_case_log crm.group_crm_user 1 1 1 1
10 access_crm_case_history crm.case.history model_crm_case_history crm.group_crm_user 1 1 1 1
11 access_crm_case_section_manager o crm.case.section.manager model_crm_case_section crm.group_crm_manager 1 1 1 1
12 access_crm_case_categ_manager crm.case.categ.manager model_crm_case_categ crm.group_crm_manager 1 1 1 1
13 access_crm_case_log_manager crm.case.log manager model_crm_case_log crm.group_crm_manager 1 1 1 1
14 access_crm_case_history_manager crm.case.history manager model_crm_case_history crm.group_crm_manager 1 1 1 1
access_crm_email_add_cc_manager crm.email.add.cc model_crm_email_add_cc crm.group_crm_manager 1 1 1 1
access_crm_email_add_cc_user crm.email.add.cc model_crm_email_add_cc crm.group_crm_user 1 0 0 0
15 access_crm_case_stage crm.case.stage model_crm_case_stage crm.group_crm_user 1 0 0 0
16 access_crm_case_stage_manager crm.case.stage model_crm_case_stage crm.group_crm_manager 1 1 1 1
17 access_crm_case_resource_type_user crm_case_resource_type user model_crm_case_resource_type crm.group_crm_user 1 0 0 0
18 access_crm_case_resource_type_manager crm_case_resource_type manager model_crm_case_resource_type crm.group_crm_manager 1 1 1 1
access_crm_case_report_user crm.case.report model_crm_case_report crm.group_crm_user 1 0 0 0
access_crm_case_report_manager crm.case.report manager model_crm_case_report crm.group_crm_manager 1 1 1 1
access_crm_claim_report_user crm.claim.report model_crm_claim_report crm.group_crm_user 1 0 0 0
access_crm_fundraising_report_user crm.fundraising.report model_crm_fundraising_report crm.group_crm_user 1 0 0 0
19 access_crm_lead_report_user crm.lead.report model_crm_lead_report crm.group_crm_user 1 0 0 0
20 access_crm_phonecall_report_user crm.phonecall.report model_crm_phonecall_report crm.group_crm_user 1 0 0 0
access_crm_opportunity_report_user crm.opportunity.report model_crm_opportunity_report crm.group_crm_user 1 0 0 0
access_report_crm_case_service_dashboard report.crm.case.service.dashboard model_report_crm_case_service_dashboard crm.group_crm_user 1 0 0 0
21 access_crm_lead2partner crm.lead2partner model_crm_lead2partner crm.group_crm_user 1 1 1 1
22 access_crm_lead2opportunity crm.lead2opportunity model_crm_lead2opportunity crm.group_crm_user 1 1 1 1
23 access_crm_opportunity2phonecall crm.opportunity2phonecall model_crm_opportunity2phonecall crm.group_crm_user 1 1 1 1
24 access_crm_phonecall2phonecall crm.phonecall2phonecall model_crm_phonecall2phonecall crm.group_crm_user 1 1 1 1
25 access_crm_phonecall2partner crm.phonecall2partner model_crm_phonecall2partner crm.group_crm_user 1 1 1 1
26 access_crm_phonecall2opportunity crm.phonecall2opportunity model_crm_phonecall2opportunity crm.group_crm_user 1 1 1 1
access_report_crm_helpdesk report.crm.helpdesk model_crm_helpdesk_report crm.group_crm_user 1 1 1 1
27 access_crm_send_mail crm.send.mail model_crm_send_mail crm.group_crm_user 1 1 1 1
28 access_crm_send_mail_attachment crm.send.mail.attachment model_crm_send_mail_attachment crm.group_crm_user 1 1 1 1
29 access_crm_partner2opportunity crm.partner2opportunity model_crm_partner2opportunity crm.group_crm_user 1 1 1 1
30 access_crm_lead2opportunity_partner crm.lead2opportunity.partner model_crm_lead2opportunity_partner crm.group_crm_user 1 1 1 1
31 access_crm_installer crm.installer.rule model_crm_installer crm.group_crm_user 1 1 1 1
32 access_crm_lead_forward_to_partner crm.lead.forward.to.partner model_crm_lead_forward_to_partner crm.group_crm_user 2 2 2 2

View File

@ -0,0 +1,112 @@
- |
In order to test the CRM in OpenERP,
I will do a customer qualification process that
starts with a fist contact with a customer (a lead), which will be converted to a
business opportunity and a partner.
- |
In order to test the flow, I create a two new users "user_crm" and
I assign the group "salesman".
-
!record {model: res.users, id: res_users_usercrm0}:
company_id: base.main_company
context_lang: en_US
context_section_id: crm.section_sales_department
groups_id:
- crm.group_crm_user
login: user_crm
name: user_crm
password: user_crm
- |
I start by creating a new lead "New Customer" and I provide an address to this
new customer, as well as an email "info@mycustomer.com".
-
!record {model: crm.lead, id: crm_lead_newcustomer0}:
email_from: info@mycustomer.com
name: New Customer
partner_name: Sandip zala
phone: (855) 924-4364
mobile: (333) 715-1450
section_id: crm.section_sales_department
referred: False
title: M.
- |
I check that the lead is in 'draft' state,
-
!assert {model: crm.lead, id: crm_lead_newcustomer0, string: Lead in Draft}:
- state == "draft"
-
I open lead by click on "Open" button,
-
!python {model: crm.lead}: |
self.case_open(cr, uid, [ref("crm_lead_newcustomer0")])
- |
As the lead seems to be a real business opportunity, I will convert it to a
partner
and a business opportunity by clicking on the "Convert" button.
-
!python {model: crm.lead}: |
lead = self.browse(cr, uid, ref('crm_lead_newcustomer0'))
if not lead.partner_id:
self.convert_opportunity(cr, uid, [ref("crm_lead_newcustomer0")], {'active_ids': [ref("crm_lead_newcustomer0")]})
-
|
Now, select "create a new partner" option in this wizard.
-
!record {model: crm.lead2opportunity.partner, id: crm_lead2opportunity_partner_create_0}:
action: 'create'
-
I click on "Continue" button of this wizard.
-
!python {model: crm.lead2opportunity.partner}: |
self.make_partner(cr, uid, [ref("crm_lead2opportunity_partner_create_0")], {'active_ids': [ref("crm_lead_newcustomer0")]})
-
Now, I give value to this wizard field.
-
!record {model: crm.lead2opportunity, id: crm_lead2opportunity_stonage_0}:
name: Sandip zala
planned_revenue: 0.00
probability: 0.00
-
Then, Click on "Create Opportunity" button of this wizard.
-
!python {model: crm.lead2opportunity}: |
self.action_apply(cr, uid, [ref('crm_lead2opportunity_stonage_0')], {'active_id': ref('crm_lead_newcustomer0')})
- |
I can check that a lead and a business opportunity is now assigned to this
lead.
-
# !python {model: crm.lead, id: crm_lead_newcustomer0}:
# - opportunity_id.id != False
- |
I check that the partner associated to this lead as the same country, phone number
and name than the opportunity.
-
!python {model: crm.lead}: |
lead = self.browse(cr, uid, ref("crm_lead_newcustomer0"))
obj_opportunity = self.pool.get('crm.lead')
ids = obj_opportunity.search(cr, uid, [('name', '=', lead.partner_name)])
opportunity = obj_opportunity.browse(cr, uid, ids)[0]
assert lead.partner_name == opportunity.partner_id.name
assert lead.phone == opportunity.phone
-
|
# yaml is also not working smpt server and send new email.
- |
I configure with smtp server.
- |
And I communicate with lead through send New mail to Lead using it email address from user which are loged.
- |
I check that communication history generated when send email to lead.
- |
Then, I add a cc which receive copy of future communication between partner and users by mail.
- |
I Reply to last Email to lead with some document attached.and check that communication history generated or not.

View File

@ -0,0 +1,99 @@
- |
Now I will test Meetings which may be customer meeting or phonecall meeting or
internal Meeting.
- |
I start by creating a new Meeting.
-
!record {model: crm.meeting, id: crm_meeting_regardingpresentation0}:
categ_id: crm.categ_meet2
date: '2010-04-21 16:04:00'
date_deadline: '2010-04-22 00:04:00'
duration: 8.0
email_from: info@balmerinc.be
location: Ahmedabad
name: Regarding Presentation
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
rrule_type: weekly
section_id: crm.section_sales_department
- |
I check that the Meetings is in 'UnConfirmed' state.
-
!assert {model: crm.meeting, id: crm_meeting_regardingpresentation0}:
- state == "draft"
- |
I can set reminder on meeting if I put reminder "40 minutes before"
- |
For that, I first create alarm.
-
!record {model: res.alarm, id: res_alarm_minituesbefore0}:
name: 40 minutes before
trigger_duration: 40
trigger_interval: minutes
trigger_occurs: before
trigger_related: start
- |
Now I will assign this reminder.
- !python {model: crm.meeting}: |
self.write(cr, uid, [ref('crm_meeting_regardingpresentation0')], {'alarm_id': ref("res_alarm_minituesbefore0")})
- |
In order to check recurrence on meetings I will set Recurrency to Custom
and I set the fields so that the meeting will occure weekly on Monday and Friday 10 times
-
!python {model: crm.meeting}: |
self.write(cr, uid, [ref("crm_meeting_regardingpresentation0")], {'fr': 1, 'mo': 1, 'th': 1, 'tu': 1, 'we':1, 'count':10, 'interval': 1, 'freq': 'weekly', 'rrule_type': 'custom'})
- |
I can see from the calendar view that the meeting is scheduled on Monday and Friday
for 10 times,
-
!python {model: crm.meeting}: |
self.fields_view_get(cr, uid, False, 'calendar', context)
- |
I will search for one of the recurrent event and count the number of meeting.
-
!python {model: crm.meeting}: |
ids = self.search(cr, uid, [('date', '>=', '2010-05-21 00:00:00'), ('date', '<=', '2010-04-21 00:00:00')] )
assert len(ids) == 9
- |
Now If I want to edit meetings information for all occurence I click on "Edit All" button.
-
!python {model: crm.meeting}: |
self.open_meeting(cr, uid, [ref('crm_meeting_regardingpresentation0')])
- |
I can see that new meeting form is opened with same value
I change some data for meeting and save it
I can see from meeting's calendar view that all meeting occurences are changed accordingly
-
!record {model: crm.meeting, id: crm.crm_meeting_regardingpresentation0}:
alarm_id: base_calendar.alarm9
rrule_type: weekly
- |
In order to invite people for this meetings, I click on "Invite People" button
I can invite internal user.
-
!record {model: base_calendar.invite.attendee, id: base_calendar_invite_attendee_0}:
type: internal
partner_id: base.res_partner_9
user_ids:
- base.user_demo
- |
If I set Send mail boolean as True I can see that an email is send to
specified email address with proper meeting information.
-
#This is not working for send mail boolean as True.
-
I click on "Invite" button of "Invite attendee" wizard.
-
!python {model: base_calendar.invite.attendee}: |
self.do_invite(cr, uid, [ref('base_calendar_invite_attendee_0')], {'active_id': ref('crm_meeting_regardingpresentation0'), 'model' : 'crm.meeting', 'attendee_field':'attendee_ids'})
- |
After direct/indirect confirmation for meetings I can confirm meeting
-
!python {model: crm.meeting}: |
self.case_open(cr, uid, [ref('crm_meeting_regardingpresentation0')])

View File

@ -0,0 +1,81 @@
- |
I start by creating a new Opportunity. And I select partner for opportunity.
I can see that after selecting partner his contact and email is automatically filled.
-
!record {model: crm.lead, id: crm_opportunity_abcfuelcounits0}:
email_from: info@balmerinc.be
name: 'ABC FUEL CO 829264 - 10002 units '
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
probability: 1.0
stage_id: crm.stage_oppor1
categ_id: crm.categ_oppor2
section_id: crm.section_sales_department
- |
I check that the opportunity is in 'New' state.
-
!assert {model: crm.lead, id: crm_opportunity_abcfuelcounits0}:
- state == "draft"
- |
I open opportunity by click on "Open" button,
-
!python {model: crm.lead}: |
self.case_open(cr, uid, [ref("crm_opportunity_abcfuelcounits0")])
- |
I schedule Meeting on this current opportunity by clicking on "schedule
Meeting".
-
!python {model: crm.lead}: |
self.action_makeMeeting(cr, uid, [ref("crm_opportunity_abcfuelcounits0")])
- |
I can see that Meeting's calendar view is shown.
then I click on the date on which I want schedule meeting.
I fill proper data for that meeting and save it
-
!record {model: crm.meeting, id: crm_meeting_abcfuelcounits0}:
alarm_id: base_calendar.alarm3
date: '2010-04-16 00:00:00'
date_deadline: '2010-04-16 08:00:00'
duration: 8.0
email_from: info@balmerinc.be
name: 'ABC FUEL CO 829264 - 10002 units '
opportunity_id: 'crm_opportunity_abcfuelcounits0'
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
rrule_type: weekly
section_id: crm.section_sales_department
state: open
- |
In order to schedule a phonecall to the partner
I click on "schedule call" button and select planned date for the call.
-
!record {model: crm.opportunity2phonecall, id: crm_opportunity2phonecall_abcfuelcounits0}:
date: '2010-04-17 11:15:00'
name: 'ABC FUEL CO 829264 - 10002 units '
section_id: crm.section_sales_department
user_id: base.user_demo
- |
schedule phonecall by apply (action_apply)function of opportunity2phoncall wizard
-
!python {model: crm.opportunity2phonecall}: |
self.action_apply(cr, uid, [ref('crm_opportunity2phonecall_abcfuelcounits0')], {'active_ids': [ref("crm_opportunity_abcfuelcounits0")]})
- |
I check that phonecall record is created for that opportunity.
-
!python {model: crm.phonecall}: |
ids = self.search(cr, uid, [('name', '=', 'ABC FUEL CO 829264 - 10002 units')])
assert len(ids)
- |
I can see phonecall record after click on "Schedule call" wizard.
-
!record {model: crm.phonecall, id: crm_phonecall_abcfuelcounits0}:
date: '2010-04-17 11:15:00'
duration: 3.0
name: 'ABC FUEL CO 829264 - 10002 units'
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
section_id: crm.section_sales_department

View File

@ -0,0 +1,91 @@
- |
I start by creating a new phonecall.
-
!record {model: crm.phonecall, id: crm_phonecall_interviewcall0}:
date: '2010-04-21 18:59:00'
name: Interview call
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
partner_mobile: (+32)2 211 34 83
section_id: crm.section_sales_department
- |
As the success of phonecall seems to be a real business opportunity, I will convert
it to opportunity by clicking on the "Convert to Opportunity" button.
-
# !python {model: crm.phonecall2opportunity}: |
# self.action_apply(cr, uid, [1], {'active_id': ref('crm_phonecall_interviewcall0')})
-
# This is not working, find a way to do that in YAML
- |
I can see that a business opportunity is now assigned to this phonecall
- |
I check that the partner associated to this as the same country, phone number
and name than the phonecall.
- |
In order to avoid the duplication of similar partner, OpenERP should be able to
detect if the partner exists or if it should be created from the phonecall during the
conversion.
- |
In order to test this, I log as the user "user_crm2"
- |
Then, I create the same phonecall than the preceeding one but I change the name
of the phonecall. But this customer keeps the same email.
- |
This time OpenERP should detect that this customer already exists in the
partner base and I check that in the wizard, proposes me to link to this existing
partner instead of creating another one.
- |
I confirm the conversion wizard.
- |
And I check that the phonecall and the newly created business opportunity is linked
to the partner
- |
I schedule Meeting on this current phonecall by clicking on "schedule
Meeting"
-
!python {model: crm.phonecall}: |
self.action_make_meeting(cr, uid, [ref('crm_phonecall_interviewcall0')])
- |
I can see that Meeting's calendar view is shown.
then I click on the date on which I want schedule meeting.
I fill proper data for that meeting and save it
-
!record {model: crm.meeting, id: crm_meeting_interviewcall0}:
alarm_id: base_calendar.alarm3
date: '2010-04-20 00:00:00'
date_deadline: '2010-04-20 08:00:00'
duration: 8.0
email_from: info@balmerinc.be
name: Interview call
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
phonecall_id: 'crm_phonecall_interviewcall0'
rrule_type: weekly
state: open
-
#This is not working for yaml
- |
I can jump to this meeting by "Meetings" shortcut from my phonecall's form view
-
- |
In order to schedule other phonecall to the partner
I click on "schedule other call" button. and plan for other call
I can see that it will open other phonecall view with some data same as current
phonecall.
-
!record {model: crm.phonecall2phonecall, id: crm_phonecall2phonecall_interviewcall0}:
date: '2010-04-21 19:49:00'
name: Interview call
section_id: crm.section_sales_department
user_id: base.user_root
- |
I click on "Schedule" button of this wizard.
-
!python {model: crm.phonecall2phonecall}: |
self.action_apply(cr, uid, [ref('crm_phonecall2phonecall_interviewcall0')], {'active_id': ref('crm_phonecall_interviewcall0')})

View File

@ -21,7 +21,6 @@
import crm_send_email
import crm_forward_to_partner
import crm_email_add_cc
import crm_lead_to_partner
import crm_lead_to_opportunity

View File

@ -1,106 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import osv, fields
from tools.translate import _
import tools
class crm_email_add_cc_wizard(osv.osv_memory):
""" Email Add CC"""
_name = "crm.email.add.cc"
_description = "Email Add CC"
_columns = {
'name': fields.selection([('user', 'User'), ('partner', 'Partner'), \
('email', 'Email Address')], 'Send to', required=True),
'user_id': fields.many2one('res.users', "User"),
'partner_id': fields.many2one('res.partner', "Partner"),
'email': fields.char('Email', size=32),
'subject': fields.char('Subject', size=32),
}
def on_change_email(self, cr, uid, ids, user, partner):
""" @param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of Mails IDs
"""
if (not partner and not user):
return {'value': {'email': False}}
email = False
if partner:
addr = self.pool.get('res.partner').address_get(cr, uid, [partner], ['contact'])
if addr:
email = self.pool.get('res.partner.address').read(cr, uid, addr['contact'] , ['email'])['email']
elif user:
addr = self.pool.get('res.users').read(cr, uid, user, ['address_id'])['address_id']
if addr:
email = self.pool.get('res.partner.address').read(cr, uid, addr[0] , ['email'])['email']
return {'value': {'email': email}}
def add_cc(self, cr, uid, ids, context={}):
"""
Adds CC value in case
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param ids: List of create menus IDs
@param context: A standard dictionary for contextual values
"""
data = self.read(cr, uid, ids[0])
email = data['email']
subject = data['subject']
if not context:
return {}
history_line = self.pool.get('crm.case.history').browse(cr, uid, context['active_id'])
model = history_line.log_id.model_id.model
model_pool = self.pool.get(model)
case = model_pool.browse(cr, uid, history_line.log_id.res_id)
body = history_line.description.replace('\n', '\n> ')
if not case.email_from:
raise osv.except_osv(_('Warning!'), ('No email Address defined for this case..'))
if not email:
raise osv.except_osv(_('Warning!'), ('Please Specify email address..'))
flag = tools.email_send(
case.user_id.address_id.email,
[case.email_from],
subject or '[' + str(case.id) + '] ' + case.name,
model_pool.format_body(body),
email_cc = [email],
openobject_id = str(case.id),
subtype = "html"
)
if flag:
model_pool.write(cr, uid, case.id, {'email_cc' : case.email_cc and case.email_cc + ',' + email or email})
else:
raise osv.except_osv(_('Email Fail!'), ("Lastest Email is not sent successfully"))
return {}
crm_email_add_cc_wizard()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

Some files were not shown because too many files have changed in this diff Show More