[MERGE] merge from trunk
bzr revid: ged@openerp.com-20140417065356-x7o3jg5bo5430zth
This commit is contained in:
commit
0c24df7074
|
@ -840,16 +840,11 @@ class account_journal(osv.osv):
|
||||||
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
|
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
|
||||||
if not args:
|
if not args:
|
||||||
args = []
|
args = []
|
||||||
if context is None:
|
if operator in expression.NEGATIVE_TERM_OPERATORS:
|
||||||
context = {}
|
domain = [('code', operator, name), ('name', operator, name)]
|
||||||
ids = []
|
else:
|
||||||
if context.get('journal_type', False):
|
domain = ['|', ('code', operator, name), ('name', operator, name)]
|
||||||
args += [('type','=',context.get('journal_type'))]
|
ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context)
|
||||||
if name:
|
|
||||||
ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit, context=context)
|
|
||||||
if not ids:
|
|
||||||
ids = self.search(cr, user, [('name', 'ilike', name)]+ args, limit=limit, context=context)#fix it ilike should be replace with operator
|
|
||||||
|
|
||||||
return self.name_get(cr, user, ids, context=context)
|
return self.name_get(cr, user, ids, context=context)
|
||||||
|
|
||||||
|
|
||||||
|
@ -938,13 +933,11 @@ class account_fiscalyear(osv.osv):
|
||||||
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
|
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
|
||||||
if args is None:
|
if args is None:
|
||||||
args = []
|
args = []
|
||||||
if context is None:
|
if operator in expression.NEGATIVE_TERM_OPERATORS:
|
||||||
context = {}
|
domain = [('code', operator, name), ('name', operator, name)]
|
||||||
ids = []
|
else:
|
||||||
if name:
|
domain = ['|', ('code', operator, name), ('name', operator, name)]
|
||||||
ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit)
|
ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context)
|
||||||
if not ids:
|
|
||||||
ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
|
|
||||||
return self.name_get(cr, user, ids, context=context)
|
return self.name_get(cr, user, ids, context=context)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1040,19 +1033,11 @@ class account_period(osv.osv):
|
||||||
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
|
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
|
||||||
if args is None:
|
if args is None:
|
||||||
args = []
|
args = []
|
||||||
if context is None:
|
if operator in expression.NEGATIVE_TERM_OPERATORS:
|
||||||
context = {}
|
domain = [('code', operator, name), ('name', operator, name)]
|
||||||
ids = []
|
else:
|
||||||
if name:
|
domain = ['|', ('code', operator, name), ('name', operator, name)]
|
||||||
ids = self.search(cr, user,
|
ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context)
|
||||||
[('code', 'ilike', name)] + args,
|
|
||||||
limit=limit,
|
|
||||||
context=context)
|
|
||||||
if not ids:
|
|
||||||
ids = self.search(cr, user,
|
|
||||||
[('name', operator, name)] + args,
|
|
||||||
limit=limit,
|
|
||||||
context=context)
|
|
||||||
return self.name_get(cr, user, ids, context=context)
|
return self.name_get(cr, user, ids, context=context)
|
||||||
|
|
||||||
def write(self, cr, uid, ids, vals, context=None):
|
def write(self, cr, uid, ids, vals, context=None):
|
||||||
|
@ -1187,36 +1172,6 @@ class account_move(osv.osv):
|
||||||
'company_id': company_id,
|
'company_id': company_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
|
|
||||||
"""
|
|
||||||
Returns a list of tupples containing id, name, as internally it is called {def name_get}
|
|
||||||
result format: {[(id, name), (id, name), ...]}
|
|
||||||
|
|
||||||
@param cr: A database cursor
|
|
||||||
@param user: ID of the user currently logged in
|
|
||||||
@param name: name to search
|
|
||||||
@param args: other arguments
|
|
||||||
@param operator: default operator is 'ilike', it can be changed
|
|
||||||
@param context: context arguments, like lang, time zone
|
|
||||||
@param limit: Returns first 'n' ids of complete result, default is 80.
|
|
||||||
|
|
||||||
@return: Returns a list of tuples containing id and name
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not args:
|
|
||||||
args = []
|
|
||||||
ids = []
|
|
||||||
if name:
|
|
||||||
ids += self.search(cr, user, [('name','ilike',name)]+args, limit=limit, context=context)
|
|
||||||
|
|
||||||
if not ids and name and type(name) == int:
|
|
||||||
ids += self.search(cr, user, [('id','=',name)]+args, limit=limit, context=context)
|
|
||||||
|
|
||||||
if not ids:
|
|
||||||
ids += self.search(cr, user, args, limit=limit, context=context)
|
|
||||||
|
|
||||||
return self.name_get(cr, user, ids, context=context)
|
|
||||||
|
|
||||||
def name_get(self, cursor, user, ids, context=None):
|
def name_get(self, cursor, user, ids, context=None):
|
||||||
if isinstance(ids, (int, long)):
|
if isinstance(ids, (int, long)):
|
||||||
ids = [ids]
|
ids = [ids]
|
||||||
|
@ -1842,10 +1797,12 @@ class account_tax_code(osv.osv):
|
||||||
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
|
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
|
||||||
if not args:
|
if not args:
|
||||||
args = []
|
args = []
|
||||||
if context is None:
|
if operator in expression.NEGATIVE_TERM_OPERATORS:
|
||||||
context = {}
|
domain = [('code', operator, name), ('name', operator, name)]
|
||||||
ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
|
else:
|
||||||
return self.name_get(cr, user, ids, context)
|
domain = ['|', ('code', operator, name), ('name', operator, name)]
|
||||||
|
ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context)
|
||||||
|
return self.name_get(cr, user, ids, context=context)
|
||||||
|
|
||||||
def name_get(self, cr, uid, ids, context=None):
|
def name_get(self, cr, uid, ids, context=None):
|
||||||
if isinstance(ids, (int, long)):
|
if isinstance(ids, (int, long)):
|
||||||
|
@ -1974,15 +1931,11 @@ class account_tax(osv.osv):
|
||||||
"""
|
"""
|
||||||
if not args:
|
if not args:
|
||||||
args = []
|
args = []
|
||||||
if context is None:
|
if operator in expression.NEGATIVE_TERM_OPERATORS:
|
||||||
context = {}
|
domain = [('description', operator, name), ('name', operator, name)]
|
||||||
ids = []
|
|
||||||
if name:
|
|
||||||
ids = self.search(cr, user, [('description', '=', name)] + args, limit=limit, context=context)
|
|
||||||
if not ids:
|
|
||||||
ids = self.search(cr, user, [('name', operator, name)] + args, limit=limit, context=context)
|
|
||||||
else:
|
else:
|
||||||
ids = self.search(cr, user, args, limit=limit, context=context or {})
|
domain = ['|', ('description', operator, name), ('name', operator, name)]
|
||||||
|
ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context)
|
||||||
return self.name_get(cr, user, ids, context=context)
|
return self.name_get(cr, user, ids, context=context)
|
||||||
|
|
||||||
def write(self, cr, uid, ids, vals, context=None):
|
def write(self, cr, uid, ids, vals, context=None):
|
||||||
|
|
|
@ -672,25 +672,14 @@ class account_invoice(osv.osv):
|
||||||
self.create_workflow(cr, uid, ids)
|
self.create_workflow(cr, uid, ids)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# ----------------------------------------
|
def get_formview_id(self, cr, uid, id, context=None):
|
||||||
# Mail related methods
|
|
||||||
# ----------------------------------------
|
|
||||||
|
|
||||||
def _get_formview_action(self, cr, uid, id, context=None):
|
|
||||||
""" Update form view id of action to open the invoice """
|
""" Update form view id of action to open the invoice """
|
||||||
action = super(account_invoice, self)._get_formview_action(cr, uid, id, context=context)
|
|
||||||
obj = self.browse(cr, uid, id, context=context)
|
obj = self.browse(cr, uid, id, context=context)
|
||||||
if obj.type == 'in_invoice':
|
if obj.type == 'in_invoice':
|
||||||
model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
|
model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
|
||||||
action.update({
|
|
||||||
'views': [(view_id, 'form')],
|
|
||||||
})
|
|
||||||
else:
|
else:
|
||||||
model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_form')
|
model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'invoice_form')
|
||||||
action.update({
|
return view_id
|
||||||
'views': [(view_id, 'form')],
|
|
||||||
})
|
|
||||||
return action
|
|
||||||
|
|
||||||
# Workflow stuff
|
# Workflow stuff
|
||||||
#################
|
#################
|
||||||
|
|
|
@ -165,7 +165,7 @@ class account_invoice_refund(osv.osv_memory):
|
||||||
to_reconcile_ids = {}
|
to_reconcile_ids = {}
|
||||||
for line in movelines:
|
for line in movelines:
|
||||||
if line.account_id.id == inv.account_id.id:
|
if line.account_id.id == inv.account_id.id:
|
||||||
to_reconcile_ids[line.account_id.id] = [line.id]
|
to_reconcile_ids.setdefault(line.account_id.id, []).append(line.id)
|
||||||
if line.reconcile_id:
|
if line.reconcile_id:
|
||||||
line.reconcile_id.unlink()
|
line.reconcile_id.unlink()
|
||||||
inv_obj.signal_invoice_open(cr, uid, [refund.id])
|
inv_obj.signal_invoice_open(cr, uid, [refund.id])
|
||||||
|
|
|
@ -162,7 +162,7 @@ class crossovered_budget_lines(osv.osv):
|
||||||
elapsed = strToDate(date_to) - strToDate(date_to)
|
elapsed = strToDate(date_to) - strToDate(date_to)
|
||||||
|
|
||||||
if total.days:
|
if total.days:
|
||||||
theo_amt = float(elapsed.days / float(total.days)) * line.planned_amount
|
theo_amt = float((elapsed.days + 1) / float(total.days + 1)) * line.planned_amount
|
||||||
else:
|
else:
|
||||||
theo_amt = line.planned_amount
|
theo_amt = line.planned_amount
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
|
||||||
var qdict = {
|
var qdict = {
|
||||||
current_layout : this.$el.find('.oe_dashboard').attr('data-layout')
|
current_layout : this.$el.find('.oe_dashboard').attr('data-layout')
|
||||||
};
|
};
|
||||||
var $dialog = instance.web.Dialog(this, {
|
var $dialog = new instance.web.Dialog(this, {
|
||||||
title: _t("Edit Layout"),
|
title: _t("Edit Layout"),
|
||||||
}, QWeb.render('DashBoard.layouts', qdict)).open();
|
}, QWeb.render('DashBoard.layouts', qdict)).open();
|
||||||
$dialog.find('li').click(function() {
|
$dialog.find('li').click(function() {
|
||||||
|
|
|
@ -197,6 +197,10 @@ class calendar_attendee(osv.Model):
|
||||||
@param email_from: email address for user sending the mail
|
@param email_from: email address for user sending the mail
|
||||||
"""
|
"""
|
||||||
res = False
|
res = False
|
||||||
|
|
||||||
|
if self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.block_mail', default=False):
|
||||||
|
return res
|
||||||
|
|
||||||
mail_ids = []
|
mail_ids = []
|
||||||
data_pool = self.pool['ir.model.data']
|
data_pool = self.pool['ir.model.data']
|
||||||
mailmess_pool = self.pool['mail.message']
|
mailmess_pool = self.pool['mail.message']
|
||||||
|
@ -431,7 +435,7 @@ class calendar_alarm_manager(osv.AbstractModel):
|
||||||
if cron and len(cron) == 1:
|
if cron and len(cron) == 1:
|
||||||
cron = self.pool.get('ir.cron').browse(cr, uid, cron[0], context=context)
|
cron = self.pool.get('ir.cron').browse(cr, uid, cron[0], context=context)
|
||||||
else:
|
else:
|
||||||
raise ("Cron for " + self._name + " not identified :( !")
|
_logger.exception("Cron for " + self._name + " can not be identified !")
|
||||||
|
|
||||||
if cron.interval_type == "weeks":
|
if cron.interval_type == "weeks":
|
||||||
cron_interval = cron.interval_number * 7 * 24 * 60 * 60
|
cron_interval = cron.interval_number * 7 * 24 * 60 * 60
|
||||||
|
@ -445,7 +449,7 @@ class calendar_alarm_manager(osv.AbstractModel):
|
||||||
cron_interval = cron.interval_number
|
cron_interval = cron.interval_number
|
||||||
|
|
||||||
if not cron_interval:
|
if not cron_interval:
|
||||||
raise ("Cron delay for " + self._name + " can not be calculated :( !")
|
_logger.exception("Cron delay can not be computed !")
|
||||||
|
|
||||||
all_events = self.get_next_potential_limit_alarm(cr, uid, cron_interval, notif=False, context=context)
|
all_events = self.get_next_potential_limit_alarm(cr, uid, cron_interval, notif=False, context=context)
|
||||||
|
|
||||||
|
@ -649,7 +653,7 @@ class calendar_event(osv.Model):
|
||||||
_inherit = ["mail.thread", "ir.needaction_mixin"]
|
_inherit = ["mail.thread", "ir.needaction_mixin"]
|
||||||
|
|
||||||
def do_run_scheduler(self, cr, uid, id, context=None):
|
def do_run_scheduler(self, cr, uid, id, context=None):
|
||||||
self.pool['calendar.alarm_manager'].do_run_scheduler(cr, uid, context=context)
|
self.pool['calendar.alarm_manager'].get_next_mail(cr, uid, context=context)
|
||||||
|
|
||||||
def get_recurrent_date_by_event(self, cr, uid, event, context=None):
|
def get_recurrent_date_by_event(self, cr, uid, event, context=None):
|
||||||
"""Get recurrent dates based on Rule string and all event where recurrent_id is child
|
"""Get recurrent dates based on Rule string and all event where recurrent_id is child
|
||||||
|
|
|
@ -79,6 +79,7 @@ class crm_lead(format_address, osv.osv):
|
||||||
'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.fold and obj.stage_id.sequence > 1,
|
'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.fold and obj.stage_id.sequence > 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
_mail_mass_mailing = _('Leads / Opportunities')
|
||||||
|
|
||||||
def get_empty_list_help(self, cr, uid, help, context=None):
|
def get_empty_list_help(self, cr, uid, help, context=None):
|
||||||
if context.get('default_type') == 'lead':
|
if context.get('default_type') == 'lead':
|
||||||
|
@ -980,15 +981,13 @@ class crm_lead(format_address, osv.osv):
|
||||||
return [lead.section_id.message_get_reply_to()[0] if lead.section_id else False
|
return [lead.section_id.message_get_reply_to()[0] if lead.section_id else False
|
||||||
for lead in self.browse(cr, SUPERUSER_ID, ids, context=context)]
|
for lead in self.browse(cr, SUPERUSER_ID, ids, context=context)]
|
||||||
|
|
||||||
def _get_formview_action(self, cr, uid, id, context=None):
|
def get_formview_id(self, cr, uid, id, context=None):
|
||||||
action = super(crm_lead, self)._get_formview_action(cr, uid, id, context=context)
|
|
||||||
obj = self.browse(cr, uid, id, context=context)
|
obj = self.browse(cr, uid, id, context=context)
|
||||||
if obj.type == 'opportunity':
|
if obj.type == 'opportunity':
|
||||||
model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm', 'crm_case_form_view_oppor')
|
model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm', 'crm_case_form_view_oppor')
|
||||||
action.update({
|
else:
|
||||||
'views': [(view_id, 'form')],
|
view_id = super(crm_lead, self).get_formview_id(cr, uid, id, model=model, context=context)
|
||||||
})
|
return view_id
|
||||||
return action
|
|
||||||
|
|
||||||
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
|
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
|
||||||
recipients = super(crm_lead, self).message_get_suggested_recipients(cr, uid, ids, context=context)
|
recipients = super(crm_lead, self).message_get_suggested_recipients(cr, uid, ids, context=context)
|
||||||
|
|
|
@ -63,7 +63,7 @@ campaigns on any OpenERP document.
|
||||||
'wizard/mail_compose_message_view.xml',
|
'wizard/mail_compose_message_view.xml',
|
||||||
'security/ir.model.access.csv'
|
'security/ir.model.access.csv'
|
||||||
],
|
],
|
||||||
'demo': ['res_partner_demo.yml'],
|
'demo': [],
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'auto_install': True,
|
'auto_install': True,
|
||||||
'images': ['images/1_email_account.jpeg','images/2_email_template.jpeg','images/3_emails.jpeg'],
|
'images': ['images/1_email_account.jpeg','images/2_email_template.jpeg','images/3_emails.jpeg'],
|
||||||
|
|
|
@ -231,6 +231,11 @@ class email_template(osv.osv):
|
||||||
'email_from': fields.char('From',
|
'email_from': fields.char('From',
|
||||||
help="Sender address (placeholders may be used here). If not set, the default "
|
help="Sender address (placeholders may be used here). If not set, the default "
|
||||||
"value will be the author's email alias if configured, or email address."),
|
"value will be the author's email alias if configured, or email address."),
|
||||||
|
'use_default_to': fields.boolean(
|
||||||
|
'Default recipients',
|
||||||
|
help="Default recipients of the record:\n"
|
||||||
|
"- partner (using id on a partner or the partner_id field) OR\n"
|
||||||
|
"- email (using email_from or email field)"),
|
||||||
'email_to': fields.char('To (Emails)', help="Comma-separated recipient addresses (placeholders may be used here)"),
|
'email_to': fields.char('To (Emails)', help="Comma-separated recipient addresses (placeholders may be used here)"),
|
||||||
'partner_to': fields.char('To (Partners)',
|
'partner_to': fields.char('To (Partners)',
|
||||||
help="Comma-separated ids of recipient partners (placeholders may be used here)",
|
help="Comma-separated ids of recipient partners (placeholders may be used here)",
|
||||||
|
@ -386,6 +391,37 @@ class email_template(osv.osv):
|
||||||
})
|
})
|
||||||
return {'value': result}
|
return {'value': result}
|
||||||
|
|
||||||
|
def generate_recipients_batch(self, cr, uid, results, template_id, res_ids, context=None):
|
||||||
|
"""Generates the recipients of the template. Default values can ben generated
|
||||||
|
instead of the template values if requested by template or context.
|
||||||
|
Emails (email_to, email_cc) can be transformed into partners if requested
|
||||||
|
in the context. """
|
||||||
|
if context is None:
|
||||||
|
context = {}
|
||||||
|
template = self.browse(cr, uid, template_id, context=context)
|
||||||
|
|
||||||
|
if template.use_default_to or context.get('tpl_force_default_to'):
|
||||||
|
ctx = dict(context, thread_model=template.model)
|
||||||
|
default_recipients = self.pool['mail.thread'].message_get_default_recipients(cr, uid, res_ids, context=ctx)
|
||||||
|
for res_id, recipients in default_recipients.iteritems():
|
||||||
|
results[res_id].pop('partner_to', None)
|
||||||
|
results[res_id].update(recipients)
|
||||||
|
|
||||||
|
for res_id, values in results.iteritems():
|
||||||
|
partner_ids = values.get('partner_ids', list())
|
||||||
|
if context and context.get('tpl_partners_only'):
|
||||||
|
mails = tools.email_split(values.pop('email_to', '')) + tools.email_split(values.pop('email_cc', ''))
|
||||||
|
for mail in mails:
|
||||||
|
partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context)
|
||||||
|
partner_ids.append(partner_id)
|
||||||
|
partner_to = values.pop('partner_to', '')
|
||||||
|
if partner_to:
|
||||||
|
# placeholders could generate '', 3, 2 due to some empty field values
|
||||||
|
tpl_partner_ids = [pid for pid in partner_to.split(',') if pid]
|
||||||
|
partner_ids += self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)
|
||||||
|
results[res_id]['partner_ids'] = partner_ids
|
||||||
|
return results
|
||||||
|
|
||||||
def generate_email_batch(self, cr, uid, template_id, res_ids, context=None, fields=None):
|
def generate_email_batch(self, cr, uid, template_id, res_ids, context=None, fields=None):
|
||||||
"""Generates an email from the template for given the given model based on
|
"""Generates an email from the template for given the given model based on
|
||||||
records given by res_ids.
|
records given by res_ids.
|
||||||
|
@ -420,14 +456,18 @@ class email_template(osv.osv):
|
||||||
context=context)
|
context=context)
|
||||||
for res_id, field_value in generated_field_values.iteritems():
|
for res_id, field_value in generated_field_values.iteritems():
|
||||||
results.setdefault(res_id, dict())[field] = field_value
|
results.setdefault(res_id, dict())[field] = field_value
|
||||||
|
# compute recipients
|
||||||
|
results = self.generate_recipients_batch(cr, uid, results, template.id, template_res_ids, context=context)
|
||||||
# update values for all res_ids
|
# update values for all res_ids
|
||||||
for res_id in template_res_ids:
|
for res_id in template_res_ids:
|
||||||
values = results[res_id]
|
values = results[res_id]
|
||||||
|
# body: add user signature, sanitize
|
||||||
if 'body_html' in fields and template.user_signature:
|
if 'body_html' in fields and template.user_signature:
|
||||||
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
|
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
|
||||||
values['body_html'] = tools.append_content_to_html(values['body_html'], signature)
|
values['body_html'] = tools.append_content_to_html(values['body_html'], signature)
|
||||||
if values.get('body_html'):
|
if values.get('body_html'):
|
||||||
values['body'] = tools.html_sanitize(values['body_html'])
|
values['body'] = tools.html_sanitize(values['body_html'])
|
||||||
|
# technical settings
|
||||||
values.update(
|
values.update(
|
||||||
mail_server_id=template.mail_server_id.id or False,
|
mail_server_id=template.mail_server_id.id or False,
|
||||||
auto_delete=template.auto_delete,
|
auto_delete=template.auto_delete,
|
||||||
|
@ -484,17 +524,8 @@ class email_template(osv.osv):
|
||||||
# create a mail_mail based on values, without attachments
|
# create a mail_mail based on values, without attachments
|
||||||
values = self.generate_email(cr, uid, template_id, res_id, context=context)
|
values = self.generate_email(cr, uid, template_id, res_id, context=context)
|
||||||
if not values.get('email_from'):
|
if not values.get('email_from'):
|
||||||
raise osv.except_osv(_('Warning!'),_("Sender email is missing or empty after template rendering. Specify one to deliver your message"))
|
raise osv.except_osv(_('Warning!'), _("Sender email is missing or empty after template rendering. Specify one to deliver your message"))
|
||||||
# process partner_to field that is a comma separated list of partner_ids -> recipient_ids
|
values['recipient_ids'] = [(4, pid) for pid in values.get('partner_ids', list())]
|
||||||
# NOTE: only usable if force_send is True, because otherwise the value is
|
|
||||||
# not stored on the mail_mail, and therefore lost -> fixed in v8
|
|
||||||
values['recipient_ids'] = []
|
|
||||||
partner_to = values.pop('partner_to', '')
|
|
||||||
if partner_to:
|
|
||||||
# placeholders could generate '', 3, 2 due to some empty field values
|
|
||||||
tpl_partner_ids = [pid for pid in partner_to.split(',') if pid]
|
|
||||||
values['recipient_ids'] += [(4, pid) for pid in self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)]
|
|
||||||
|
|
||||||
attachment_ids = values.pop('attachment_ids', [])
|
attachment_ids = values.pop('attachment_ids', [])
|
||||||
attachments = values.pop('attachments', [])
|
attachments = values.pop('attachments', [])
|
||||||
msg_id = mail_mail.create(cr, uid, values, context=context)
|
msg_id = mail_mail.create(cr, uid, values, context=context)
|
||||||
|
|
|
@ -9,8 +9,10 @@
|
||||||
<sheet>
|
<sheet>
|
||||||
<div class="oe_title">
|
<div class="oe_title">
|
||||||
<label for="name" class="oe_edit_only"/><h1><field name="name" required="1"/></h1>
|
<label for="name" class="oe_edit_only"/><h1><field name="name" required="1"/></h1>
|
||||||
<label for="model_id"/><field name="model_id" required="1" on_change="onchange_model_id(model_id)" class="oe_inline"/>
|
<group>
|
||||||
<field name="model" invisible="1"/>
|
<field name="model_id" required="1" on_change="onchange_model_id(model_id)"/>
|
||||||
|
<field name="model" invisible="1"/>
|
||||||
|
</group>
|
||||||
</div>
|
</div>
|
||||||
<div class="oe_right oe_button_box" name="buttons">
|
<div class="oe_right oe_button_box" name="buttons">
|
||||||
<field name="ref_ir_act_window" invisible="1"/>
|
<field name="ref_ir_act_window" invisible="1"/>
|
||||||
|
@ -30,43 +32,28 @@
|
||||||
context="{'template_id':active_id}"/>
|
context="{'template_id':active_id}"/>
|
||||||
</div>
|
</div>
|
||||||
<notebook>
|
<notebook>
|
||||||
<page string="Mailing Template">
|
<page string="Content">
|
||||||
<group>
|
<label for="subject"/>
|
||||||
<group>
|
<h2 style="display: inline-block;"><field name="subject" placeholder="Subject (placeholders may be used here)"/></h2>
|
||||||
<field name="email_from"
|
<field name="body_html"/>
|
||||||
placeholder="Override author's email"/>
|
|
||||||
<field name="email_to"
|
|
||||||
placeholder="Comma-separated recipient addresses"/>
|
|
||||||
<field name="partner_to"
|
|
||||||
placeholder="Comma-separated ids of recipient partners"/>
|
|
||||||
<field name="email_cc"
|
|
||||||
placeholder="Comma-separated carbon copy recipients addresses"/>
|
|
||||||
<field name="reply_to"
|
|
||||||
placeholder="Preferred reply address"/>
|
|
||||||
<field name="subject"
|
|
||||||
placeholder="Subject (placeholders may be used here)"/>
|
|
||||||
<field name="user_signature" string="Author Signature (mass mail only)"/>
|
|
||||||
</group>
|
|
||||||
<group class="oe_edit_only">
|
|
||||||
<h3 colspan="2">Dynamic placeholder generator</h3>
|
|
||||||
<field name="model_object_field"
|
|
||||||
domain="[('model_id','=',model_id),('ttype','!=','one2many'),('ttype','!=','many2many')]"
|
|
||||||
on_change="onchange_sub_model_object_value_field(model_object_field)"/>
|
|
||||||
<field name="sub_object" readonly="1"/>
|
|
||||||
<field name="sub_model_object_field"
|
|
||||||
domain="[('model_id','=',sub_object),('ttype','!=','one2many'),('ttype','!=','many2many')]"
|
|
||||||
attrs="{'readonly':[('sub_object','=',False)],'required':[('sub_object','!=',False)]}"
|
|
||||||
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
|
|
||||||
<field name="null_value"
|
|
||||||
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field,null_value)"/>
|
|
||||||
<field name="copyvalue"/>
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
<h3>Body</h3>
|
|
||||||
<field name="body_html" width="250" height="450"
|
|
||||||
placeholder="Rich-text/HTML content of the message (placeholders may be used here)"/>
|
|
||||||
<field name="attachment_ids" widget="many2many_binary"/>
|
<field name="attachment_ids" widget="many2many_binary"/>
|
||||||
</page>
|
</page>
|
||||||
|
<page string="Email Configuration">
|
||||||
|
<group>
|
||||||
|
<field name="email_from"
|
||||||
|
placeholder="Override author's email"/>
|
||||||
|
<field name="use_default_to"/>
|
||||||
|
<field name="email_to" attrs="{'invisible': [('use_default_to', '=', True)]}"
|
||||||
|
placeholder="Comma-separated recipient addresses"/>
|
||||||
|
<field name="partner_to" attrs="{'invisible': [('use_default_to', '=', True)]}"
|
||||||
|
placeholder="Comma-separated ids of recipient partners"/>
|
||||||
|
<field name="email_cc" attrs="{'invisible': [('use_default_to', '=', True)]}"
|
||||||
|
placeholder="Comma-separated carbon copy recipients addresses"/>
|
||||||
|
<field name="reply_to"
|
||||||
|
placeholder="Preferred reply address"/>
|
||||||
|
<field name="user_signature" string="Author Signature (mass mail only)"/>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
<page string="Advanced Settings">
|
<page string="Advanced Settings">
|
||||||
<group>
|
<group>
|
||||||
<field name="lang"/>
|
<field name="lang"/>
|
||||||
|
@ -77,6 +64,21 @@
|
||||||
attrs="{'invisible':[('report_template','=',False)]}"/>
|
attrs="{'invisible':[('report_template','=',False)]}"/>
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
|
<page string="Dynamic Placeholder Generator">
|
||||||
|
<group>
|
||||||
|
<field name="model_object_field"
|
||||||
|
domain="[('model_id','=',model_id),('ttype','!=','one2many'),('ttype','!=','many2many')]"
|
||||||
|
on_change="onchange_sub_model_object_value_field(model_object_field)"/>
|
||||||
|
<field name="sub_object" readonly="1"/>
|
||||||
|
<field name="sub_model_object_field"
|
||||||
|
domain="[('model_id','=',sub_object),('ttype','!=','one2many'),('ttype','!=','many2many')]"
|
||||||
|
attrs="{'readonly':[('sub_object','=',False)],'required':[('sub_object','!=',False)]}"
|
||||||
|
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
|
||||||
|
<field name="null_value"
|
||||||
|
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field,null_value)"/>
|
||||||
|
<field name="copyvalue"/>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
-
|
|
||||||
Set opt-out to True on all demo partners
|
|
||||||
-
|
|
||||||
!python {model: res.partner}: |
|
|
||||||
partner_ids = self.search(cr, uid, [])
|
|
||||||
# assume partners with an external ID come from demo data
|
|
||||||
ext_ids = self._get_external_ids(cr, uid, partner_ids)
|
|
||||||
ids_to_update = [k for (k,v) in ext_ids.iteritems() if v]
|
|
||||||
self.write(cr, uid, ids_to_update, {'opt_out': True})
|
|
|
@ -74,7 +74,7 @@ class test_message_compose(TestMail):
|
||||||
|
|
||||||
# 1. Comment on pigs
|
# 1. Comment on pigs
|
||||||
compose_id = mail_compose.create(cr, uid,
|
compose_id = mail_compose.create(cr, uid,
|
||||||
{'subject': 'Forget me subject', 'body': '<p>Dummy body</p>', 'post': True},
|
{'subject': 'Forget me subject', 'body': '<p>Dummy body</p>'},
|
||||||
{'default_composition_mode': 'comment',
|
{'default_composition_mode': 'comment',
|
||||||
'default_model': 'mail.group',
|
'default_model': 'mail.group',
|
||||||
'default_res_id': self.group_pigs_id,
|
'default_res_id': self.group_pigs_id,
|
||||||
|
@ -102,7 +102,7 @@ class test_message_compose(TestMail):
|
||||||
'default_template_id': email_template_id,
|
'default_template_id': email_template_id,
|
||||||
'active_ids': [self.group_pigs_id, self.group_bird_id]
|
'active_ids': [self.group_pigs_id, self.group_bird_id]
|
||||||
}
|
}
|
||||||
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body', 'post': True}, context)
|
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body'}, context)
|
||||||
compose = mail_compose.browse(cr, uid, compose_id, context)
|
compose = mail_compose.browse(cr, uid, compose_id, context)
|
||||||
onchange_res = compose.onchange_template_id(email_template_id, 'comment', 'mail.group', self.group_pigs_id)['value']
|
onchange_res = compose.onchange_template_id(email_template_id, 'comment', 'mail.group', self.group_pigs_id)['value']
|
||||||
onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
|
onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
|
||||||
|
@ -146,7 +146,7 @@ class test_message_compose(TestMail):
|
||||||
'default_partner_ids': [p_a_id],
|
'default_partner_ids': [p_a_id],
|
||||||
'active_ids': [self.group_pigs_id, self.group_bird_id]
|
'active_ids': [self.group_pigs_id, self.group_bird_id]
|
||||||
}
|
}
|
||||||
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body', 'post': True}, context)
|
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body'}, context)
|
||||||
compose = mail_compose.browse(cr, uid, compose_id, context)
|
compose = mail_compose.browse(cr, uid, compose_id, context)
|
||||||
onchange_res = compose.onchange_template_id(email_template_id, 'mass_mail', 'mail.group', self.group_pigs_id)['value']
|
onchange_res = compose.onchange_template_id(email_template_id, 'mass_mail', 'mail.group', self.group_pigs_id)['value']
|
||||||
onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
|
onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
|
||||||
|
@ -172,12 +172,12 @@ class test_message_compose(TestMail):
|
||||||
self.assertIn(_body_html1, message_pigs.body, 'mail.message body on Pigs incorrect')
|
self.assertIn(_body_html1, message_pigs.body, 'mail.message body on Pigs incorrect')
|
||||||
self.assertIn(_body_html2, message_bird.body, 'mail.message body on Bird incorrect')
|
self.assertIn(_body_html2, message_bird.body, 'mail.message body on Bird incorrect')
|
||||||
# Test: partner_ids: p_a_id (default) + 3 newly created partners
|
# Test: partner_ids: p_a_id (default) + 3 newly created partners
|
||||||
message_pigs_pids = [partner.id for partner in message_pigs.notified_partner_ids]
|
# message_pigs_pids = [partner.id for partner in message_pigs.notified_partner_ids]
|
||||||
message_bird_pids = [partner.id for partner in message_bird.notified_partner_ids]
|
# message_bird_pids = [partner.id for partner in message_bird.notified_partner_ids]
|
||||||
partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])])
|
# partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])])
|
||||||
partner_ids.append(p_a_id)
|
# partner_ids.append(p_a_id)
|
||||||
self.assertEqual(set(message_pigs_pids), set(partner_ids), 'mail.message on pigs incorrect number of notified_partner_ids')
|
# self.assertEqual(set(message_pigs_pids), set(partner_ids), 'mail.message on pigs incorrect number of notified_partner_ids')
|
||||||
self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird notified_partner_ids incorrect')
|
# self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird notified_partner_ids incorrect')
|
||||||
|
|
||||||
# ----------------------------------------
|
# ----------------------------------------
|
||||||
# CASE4: test newly introduced partner_to field
|
# CASE4: test newly introduced partner_to field
|
||||||
|
@ -237,8 +237,8 @@ class test_message_compose(TestMail):
|
||||||
email_template.send_mail(cr, uid, email_template_id, self.group_pigs_id, force_send=True, context=context)
|
email_template.send_mail(cr, uid, email_template_id, self.group_pigs_id, force_send=True, context=context)
|
||||||
sent_emails = self._build_email_kwargs_list
|
sent_emails = self._build_email_kwargs_list
|
||||||
email_to_lst = [
|
email_to_lst = [
|
||||||
['b@b.b', 'c@c.c'], ['"Followers of Pigs" <admin@yourcompany.example.com>'],
|
['b@b.b', 'c@c.c'], ['Administrator <admin@yourcompany.example.com>'],
|
||||||
['"Followers of Pigs" <raoul@raoul.fr>'], ['"Followers of Pigs" <bert@bert.fr>']]
|
['Raoul Grosbedon <raoul@raoul.fr>'], ['Bert Tartignole <bert@bert.fr>']]
|
||||||
self.assertEqual(len(sent_emails), 4, 'email_template: send_mail: 3 valid email recipients + email_to -> should send 4 emails')
|
self.assertEqual(len(sent_emails), 4, 'email_template: send_mail: 3 valid email recipients + email_to -> should send 4 emails')
|
||||||
for email in sent_emails:
|
for email in sent_emails:
|
||||||
self.assertIn(email['email_to'], email_to_lst, 'email_template: send_mail: wrong email_recipients')
|
self.assertIn(email['email_to'], email_to_lst, 'email_template: send_mail: wrong email_recipients')
|
||||||
|
|
|
@ -66,6 +66,7 @@ class email_template_preview(osv.osv_memory):
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'res_id': fields.selection(_get_records, 'Sample Document'),
|
'res_id': fields.selection(_get_records, 'Sample Document'),
|
||||||
|
'partner_ids': fields.many2many('res.partner', string='Recipients'),
|
||||||
}
|
}
|
||||||
|
|
||||||
def on_change_res_id(self, cr, uid, ids, res_id, context=None):
|
def on_change_res_id(self, cr, uid, ids, res_id, context=None):
|
||||||
|
@ -80,7 +81,7 @@ class email_template_preview(osv.osv_memory):
|
||||||
|
|
||||||
# generate and get template values
|
# generate and get template values
|
||||||
mail_values = email_template.generate_email(cr, uid, template_id, res_id, context=context)
|
mail_values = email_template.generate_email(cr, uid, template_id, res_id, context=context)
|
||||||
vals = dict((field, mail_values.get(field, False)) for field in ('email_from', 'email_to', 'email_cc', 'reply_to', 'subject', 'body_html', 'partner_to'))
|
vals = dict((field, mail_values.get(field, False)) for field in ('email_from', 'email_to', 'email_cc', 'reply_to', 'subject', 'body_html', 'partner_to', 'partner_ids', 'attachment_ids'))
|
||||||
vals['name'] = template.name
|
vals['name'] = template.name
|
||||||
return {'value': vals}
|
return {'value': vals}
|
||||||
|
|
||||||
|
|
|
@ -8,14 +8,17 @@
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Email Preview" version="7.0">
|
<form string="Email Preview" version="7.0">
|
||||||
<field name="model_id" invisible="1"/>
|
<field name="model_id" invisible="1"/>
|
||||||
<h2 style="color: #7c7bad;">Preview of <field name="name" readonly="1" nolabel="1" class="oe_inline"/></h2>
|
<h3>Preview of <field name="name" readonly="1" nolabel="1" class="oe_inline"/></h3>
|
||||||
Using sample document <field name="res_id" on_change="on_change_res_id(res_id, context)" class="oe_inline"/>
|
Choose an example <field name="model_id" class="oe_inline" readonly="1"/> record:
|
||||||
|
<field name="res_id" on_change="on_change_res_id(res_id, context)" class="oe_inline"
|
||||||
|
style="margin-left: 8px;"/>
|
||||||
<group>
|
<group>
|
||||||
<field name="subject" readonly="1"/>
|
<field name="subject" readonly="1"/>
|
||||||
<field name="email_from" readonly="1"
|
<field name="email_from" readonly="1"
|
||||||
attrs="{'invisible':[('email_from','=',False)]}"/>
|
attrs="{'invisible':[('email_from','=',False)]}"/>
|
||||||
<field name="email_to" readonly="1"/>
|
<field name="partner_ids" widget="many2many_tags" readonly="1"/>
|
||||||
<field name="partner_to" readonly="1"/>
|
<field name="email_to" readonly="1"
|
||||||
|
attrs="{'invisible':[('email_to','=',False)]}"/>
|
||||||
<field name="email_cc" readonly="1"
|
<field name="email_cc" readonly="1"
|
||||||
attrs="{'invisible':[('email_cc','=',False)]}"/>
|
attrs="{'invisible':[('email_cc','=',False)]}"/>
|
||||||
<field name="reply_to" readonly="1"
|
<field name="reply_to" readonly="1"
|
||||||
|
@ -23,6 +26,7 @@
|
||||||
</group>
|
</group>
|
||||||
<field name="body_html" widget="html" readonly="1"
|
<field name="body_html" widget="html" readonly="1"
|
||||||
nolabel="1" options='{"safe": True}'/>
|
nolabel="1" options='{"safe": True}'/>
|
||||||
|
<field name="attachment_ids" widget="many2many_binary" radonly="1"/>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
@ -30,10 +34,11 @@
|
||||||
<record id="wizard_email_template_preview" model="ir.actions.act_window">
|
<record id="wizard_email_template_preview" model="ir.actions.act_window">
|
||||||
<field name="name">Template Preview</field>
|
<field name="name">Template Preview</field>
|
||||||
<field name="res_model">email_template.preview</field>
|
<field name="res_model">email_template.preview</field>
|
||||||
<field name="src_model">email_template.preview</field>
|
<field name="src_model">email.template</field>
|
||||||
<field name="type">ir.actions.act_window</field>
|
<field name="type">ir.actions.act_window</field>
|
||||||
<field name="view_type">form</field>
|
<field name="view_type">form</field>
|
||||||
<field name="view_mode">form</field>
|
<field name="view_mode">form</field>
|
||||||
|
<field name="view_id" ref="email_template_preview_form"/>
|
||||||
<field name="auto_refresh" eval="1" />
|
<field name="auto_refresh" eval="1" />
|
||||||
<field name="target">new</field>
|
<field name="target">new</field>
|
||||||
<field name="context">{'template_id':active_id}</field>
|
<field name="context">{'template_id':active_id}</field>
|
||||||
|
|
|
@ -42,7 +42,8 @@ class mail_compose_message(osv.TransientModel):
|
||||||
_inherit = 'mail.compose.message'
|
_inherit = 'mail.compose.message'
|
||||||
|
|
||||||
def default_get(self, cr, uid, fields, context=None):
|
def default_get(self, cr, uid, fields, context=None):
|
||||||
""" Override to pre-fill the data when having a template in single-email mode """
|
""" Override to pre-fill the data when having a template in single-email mode
|
||||||
|
and not going through the view: the on_change is not called in that case. """
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
res = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
|
res = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
|
||||||
|
@ -50,19 +51,13 @@ class mail_compose_message(osv.TransientModel):
|
||||||
res.update(
|
res.update(
|
||||||
self.onchange_template_id(
|
self.onchange_template_id(
|
||||||
cr, uid, [], context['default_template_id'], res.get('composition_mode'),
|
cr, uid, [], context['default_template_id'], res.get('composition_mode'),
|
||||||
res.get('model'), res.get('res_id', context.get('active_id')), context=context
|
res.get('model'), res.get('res_id'), context=context
|
||||||
)['value']
|
)['value']
|
||||||
)
|
)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'template_id': fields.many2one('email.template', 'Use template', select=True),
|
'template_id': fields.many2one('email.template', 'Use template', select=True),
|
||||||
'partner_to': fields.char('To (Partner IDs)',
|
|
||||||
help="Comma-separated list of recipient partners ids (placeholders may be used here)"),
|
|
||||||
'email_to': fields.char('To (Emails)',
|
|
||||||
help="Comma-separated recipient addresses (placeholders may be used here)",),
|
|
||||||
'email_cc': fields.char('Cc (Emails)',
|
|
||||||
help="Carbon copy recipients (placeholders may be used here)"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def send_mail(self, cr, uid, ids, context=None):
|
def send_mail(self, cr, uid, ids, context=None):
|
||||||
|
@ -92,14 +87,13 @@ class mail_compose_message(osv.TransientModel):
|
||||||
""" - mass_mailing: we cannot render, so return the template values
|
""" - mass_mailing: we cannot render, so return the template values
|
||||||
- normal mode: return rendered values """
|
- normal mode: return rendered values """
|
||||||
if template_id and composition_mode == 'mass_mail':
|
if template_id and composition_mode == 'mass_mail':
|
||||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'mail_server_id']
|
fields = ['subject', 'body_html', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id']
|
||||||
template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
|
template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
|
||||||
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
||||||
elif template_id:
|
elif template_id:
|
||||||
values = self.generate_email_for_composer_batch(cr, uid, template_id, [res_id], context=context)[res_id]
|
values = self.generate_email_for_composer_batch(cr, uid, template_id, [res_id], context=context)[res_id]
|
||||||
# transform attachments into attachment_ids; not attached to the document because this will
|
# transform attachments into attachment_ids; not attached to the document because this will
|
||||||
# be done further in the posting process, allowing to clean database if email not send
|
# be done further in the posting process, allowing to clean database if email not send
|
||||||
values['attachment_ids'] = values.pop('attachment_ids', [])
|
|
||||||
ir_attach_obj = self.pool.get('ir.attachment')
|
ir_attach_obj = self.pool.get('ir.attachment')
|
||||||
for attach_fname, attach_datas in values.pop('attachments', []):
|
for attach_fname, attach_datas in values.pop('attachments', []):
|
||||||
data_attach = {
|
data_attach = {
|
||||||
|
@ -110,7 +104,7 @@ class mail_compose_message(osv.TransientModel):
|
||||||
'res_id': 0,
|
'res_id': 0,
|
||||||
'type': 'binary', # override default_type from context, possibly meant for another model!
|
'type': 'binary', # override default_type from context, possibly meant for another model!
|
||||||
}
|
}
|
||||||
values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context))
|
values.setdefault('attachment_ids', list()).append(ir_attach_obj.create(cr, uid, data_attach, context=context))
|
||||||
else:
|
else:
|
||||||
values = self.default_get(cr, uid, ['subject', 'body', 'email_from', 'email_to', 'email_cc', 'partner_to', 'reply_to', 'attachment_ids', 'mail_server_id'], context=context)
|
values = self.default_get(cr, uid, ['subject', 'body', 'email_from', 'email_to', 'email_cc', 'partner_to', 'reply_to', 'attachment_ids', 'mail_server_id'], context=context)
|
||||||
|
|
||||||
|
@ -148,47 +142,29 @@ class mail_compose_message(osv.TransientModel):
|
||||||
# Wizard validation and send
|
# Wizard validation and send
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
|
|
||||||
def _get_or_create_partners_from_values(self, cr, uid, rendered_values, context=None):
|
|
||||||
""" Check for email_to, email_cc, partner_to """
|
|
||||||
partner_ids = []
|
|
||||||
mails = tools.email_split(rendered_values.pop('email_to', '')) + tools.email_split(rendered_values.pop('email_cc', ''))
|
|
||||||
for mail in mails:
|
|
||||||
partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context)
|
|
||||||
partner_ids.append(partner_id)
|
|
||||||
partner_to = rendered_values.pop('partner_to', '')
|
|
||||||
if partner_to:
|
|
||||||
# placeholders could generate '', 3, 2 due to some empty field values
|
|
||||||
tpl_partner_ids = [pid for pid in partner_to.split(',') if pid]
|
|
||||||
partner_ids += self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)
|
|
||||||
return partner_ids
|
|
||||||
|
|
||||||
def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None, fields=None):
|
def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None, fields=None):
|
||||||
""" Call email_template.generate_email(), get fields relevant for
|
""" Call email_template.generate_email(), get fields relevant for
|
||||||
mail.compose.message, transform email_cc and email_to into partner_ids """
|
mail.compose.message, transform email_cc and email_to into partner_ids """
|
||||||
# filter template values
|
if context is None:
|
||||||
|
context = {}
|
||||||
if fields is None:
|
if fields is None:
|
||||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'mail_server_id']
|
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'mail_server_id']
|
||||||
returned_fields = fields + ['attachments']
|
returned_fields = fields + ['partner_ids', 'attachments']
|
||||||
values = dict.fromkeys(res_ids, False)
|
values = dict.fromkeys(res_ids, False)
|
||||||
|
|
||||||
template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, fields=fields, context=context)
|
ctx = dict(context, tpl_partners_only=True)
|
||||||
|
template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, fields=fields, context=ctx)
|
||||||
for res_id in res_ids:
|
for res_id in res_ids:
|
||||||
res_id_values = dict((field, template_values[res_id][field]) for field in returned_fields if template_values[res_id].get(field))
|
res_id_values = dict((field, template_values[res_id][field]) for field in returned_fields if template_values[res_id].get(field))
|
||||||
res_id_values['body'] = res_id_values.pop('body_html', '')
|
res_id_values['body'] = res_id_values.pop('body_html', '')
|
||||||
|
|
||||||
# transform email_to, email_cc into partner_ids
|
|
||||||
ctx = dict((k, v) for k, v in (context or {}).items() if not k.startswith('default_'))
|
|
||||||
partner_ids = self._get_or_create_partners_from_values(cr, uid, res_id_values, context=ctx)
|
|
||||||
# legacy template behavior: void values do not erase existing values and the
|
|
||||||
# related key is removed from the values dict
|
|
||||||
if partner_ids:
|
|
||||||
res_id_values['partner_ids'] = list(partner_ids)
|
|
||||||
|
|
||||||
values[res_id] = res_id_values
|
values[res_id] = res_id_values
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
|
def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
|
||||||
""" Override to handle templates. """
|
""" Override to handle templates. """
|
||||||
|
# generate composer values
|
||||||
|
composer_values = super(mail_compose_message, self).render_message_batch(cr, uid, wizard, res_ids, context)
|
||||||
|
|
||||||
# generate template-based values
|
# generate template-based values
|
||||||
if wizard.template_id:
|
if wizard.template_id:
|
||||||
template_values = self.generate_email_for_composer_batch(
|
template_values = self.generate_email_for_composer_batch(
|
||||||
|
@ -196,17 +172,18 @@ class mail_compose_message(osv.TransientModel):
|
||||||
fields=['email_to', 'partner_to', 'email_cc', 'attachment_ids', 'mail_server_id'],
|
fields=['email_to', 'partner_to', 'email_cc', 'attachment_ids', 'mail_server_id'],
|
||||||
context=context)
|
context=context)
|
||||||
else:
|
else:
|
||||||
template_values = dict.fromkeys(res_ids, dict())
|
template_values = {}
|
||||||
# generate composer values
|
|
||||||
composer_values = super(mail_compose_message, self).render_message_batch(cr, uid, wizard, res_ids, context)
|
|
||||||
|
|
||||||
for res_id in res_ids:
|
for res_id in res_ids:
|
||||||
# remove attachments from template values as they should not be rendered
|
if template_values.get(res_id):
|
||||||
template_values[res_id].pop('attachment_ids', None)
|
# recipients are managed by the template
|
||||||
# remove some keys from composer that are readonly
|
composer_values[res_id].pop('partner_ids')
|
||||||
composer_values[res_id].pop('email_to', None)
|
composer_values[res_id].pop('email_to')
|
||||||
composer_values[res_id].pop('email_cc', None)
|
composer_values[res_id].pop('email_cc')
|
||||||
composer_values[res_id].pop('partner_to', None)
|
# remove attachments from template values as they should not be rendered
|
||||||
|
template_values[res_id].pop('attachment_ids', None)
|
||||||
|
else:
|
||||||
|
template_values[res_id] = dict()
|
||||||
# update template values by composer values
|
# update template values by composer values
|
||||||
template_values[res_id].update(composer_values[res_id])
|
template_values[res_id].update(composer_values[res_id])
|
||||||
return template_values
|
return template_values
|
||||||
|
|
|
@ -7,22 +7,6 @@
|
||||||
<field name="model">mail.compose.message</field>
|
<field name="model">mail.compose.message</field>
|
||||||
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
|
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//field[@name='subject']" position="after">
|
|
||||||
<label string="Template Recipients" for="partner_to"
|
|
||||||
groups="base.group_no_one"
|
|
||||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
|
||||||
<div groups="base.group_no_one" name="template_recipients"
|
|
||||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}">
|
|
||||||
<group class="oe_grey">
|
|
||||||
<!-- <label string="Partners" for="partner_to"/> -->
|
|
||||||
<field name="partner_to" readonly="1"/>
|
|
||||||
<!-- <label string="Email To" for="email_to"/> -->
|
|
||||||
<field name="email_to" readonly="1"/>
|
|
||||||
<!-- <label string="Email CC" for="email_cc"/> -->
|
|
||||||
<field name="email_cc" readonly="1"/>
|
|
||||||
</group>
|
|
||||||
</div>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//footer" position="inside">
|
<xpath expr="//footer" position="inside">
|
||||||
<group class="oe_right oe_form" col="1">
|
<group class="oe_right oe_form" col="1">
|
||||||
<div>Use template
|
<div>Use template
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<openerp>
|
<openerp>
|
||||||
<!-- Mail template is done in a NOUPDATE block
|
<!-- Mail template is done in a NOUPDATE block
|
||||||
so users can freely customize/delete them -->
|
so users can freely customize/delete them -->
|
||||||
<data noupdate="0">
|
<data noupdate="1">
|
||||||
<!--Email template -->
|
<!--Email template -->
|
||||||
|
|
||||||
<record id="email_template_goal_reminder" model="email.template">
|
<record id="email_template_goal_reminder" model="email.template">
|
||||||
|
@ -164,7 +164,7 @@
|
||||||
<field name="period">once</field>
|
<field name="period">once</field>
|
||||||
<field name="visibility_mode">personal</field>
|
<field name="visibility_mode">personal</field>
|
||||||
<field name="report_message_frequency">never</field>
|
<field name="report_message_frequency">never</field>
|
||||||
<field name="autojoin_group_id" eval="ref('base.group_user')" />
|
<field name="user_domain">[('groups_id', 'in', ref('base.group_user'))]</field>
|
||||||
<field name="state">inprogress</field>
|
<field name="state">inprogress</field>
|
||||||
<field name="category">other</field>
|
<field name="category">other</field>
|
||||||
</record>
|
</record>
|
||||||
|
@ -174,7 +174,7 @@
|
||||||
<field name="period">once</field>
|
<field name="period">once</field>
|
||||||
<field name="visibility_mode">personal</field>
|
<field name="visibility_mode">personal</field>
|
||||||
<field name="report_message_frequency">never</field>
|
<field name="report_message_frequency">never</field>
|
||||||
<field name="user_ids" eval="[(4, ref('base.user_root'))]" />
|
<field name="user_domain">[('groups_id', 'in', ref('base.user_root'))]</field>
|
||||||
<field name="state">inprogress</field>
|
<field name="state">inprogress</field>
|
||||||
<field name="category">other</field>
|
<field name="category">other</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
|
@ -22,11 +22,13 @@
|
||||||
from openerp import SUPERUSER_ID
|
from openerp import SUPERUSER_ID
|
||||||
from openerp.osv import fields, osv
|
from openerp.osv import fields, osv
|
||||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
||||||
|
from openerp.tools.safe_eval import safe_eval as eval
|
||||||
from openerp.tools.translate import _
|
from openerp.tools.translate import _
|
||||||
|
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
import calendar
|
import calendar
|
||||||
import logging
|
import logging
|
||||||
|
import functools
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# display top 3 in ranking, could be db variable
|
# display top 3 in ranking, could be db variable
|
||||||
|
@ -115,6 +117,12 @@ class gamification_challenge(osv.Model):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _get_challenger_users(self, cr, uid, domain, context=None):
|
||||||
|
ref = functools.partial(self.pool['ir.model.data'].xmlid_to_res_id, cr, uid)
|
||||||
|
user_domain = eval(domain, {'ref': ref})
|
||||||
|
return self.pool['res.users'].search(cr, uid, user_domain, context=context)
|
||||||
|
|
||||||
|
|
||||||
_order = 'end_date, start_date, name, id'
|
_order = 'end_date, start_date, name, id'
|
||||||
_columns = {
|
_columns = {
|
||||||
'name': fields.char('Challenge Name', required=True, translate=True),
|
'name': fields.char('Challenge Name', required=True, translate=True),
|
||||||
|
@ -131,9 +139,7 @@ class gamification_challenge(osv.Model):
|
||||||
'user_ids': fields.many2many('res.users', 'user_ids',
|
'user_ids': fields.many2many('res.users', 'user_ids',
|
||||||
string='Users',
|
string='Users',
|
||||||
help="List of users participating to the challenge"),
|
help="List of users participating to the challenge"),
|
||||||
'autojoin_group_id': fields.many2one('res.groups',
|
'user_domain': fields.char('User domain', help="Alternative to a list of users"),
|
||||||
string='Auto-subscription Group',
|
|
||||||
help='Group of users whose members will be automatically added to user_ids once the challenge is started'),
|
|
||||||
|
|
||||||
'period': fields.selection([
|
'period': fields.selection([
|
||||||
('once', 'Non recurring'),
|
('once', 'Non recurring'),
|
||||||
|
@ -213,12 +219,12 @@ class gamification_challenge(osv.Model):
|
||||||
"""Overwrite the create method to add the user of groups"""
|
"""Overwrite the create method to add the user of groups"""
|
||||||
|
|
||||||
# add users when change the group auto-subscription
|
# add users when change the group auto-subscription
|
||||||
if vals.get('autojoin_group_id'):
|
if vals.get('user_domain'):
|
||||||
new_group = self.pool.get('res.groups').browse(cr, uid, vals['autojoin_group_id'], context=context)
|
user_ids = self._get_challenger_users(cr, uid, vals.get('user_domain'), context=context)
|
||||||
|
|
||||||
if not vals.get('user_ids'):
|
if not vals.get('user_ids'):
|
||||||
vals['user_ids'] = []
|
vals['user_ids'] = []
|
||||||
vals['user_ids'] += [(4, user.id) for user in new_group.users]
|
vals['user_ids'] += [(4, user_id) for user_id in user_ids]
|
||||||
|
|
||||||
create_res = super(gamification_challenge, self).create(cr, uid, vals, context=context)
|
create_res = super(gamification_challenge, self).create(cr, uid, vals, context=context)
|
||||||
|
|
||||||
|
@ -234,23 +240,12 @@ class gamification_challenge(osv.Model):
|
||||||
if isinstance(ids, (int,long)):
|
if isinstance(ids, (int,long)):
|
||||||
ids = [ids]
|
ids = [ids]
|
||||||
|
|
||||||
# add users when change the group auto-subscription
|
|
||||||
if vals.get('autojoin_group_id'):
|
|
||||||
new_group = self.pool.get('res.groups').browse(cr, uid, vals['autojoin_group_id'], context=context)
|
|
||||||
|
|
||||||
if not vals.get('user_ids'):
|
|
||||||
vals['user_ids'] = []
|
|
||||||
vals['user_ids'] += [(4, user.id) for user in new_group.users]
|
|
||||||
|
|
||||||
if vals.get('state') == 'inprogress':
|
if vals.get('state') == 'inprogress':
|
||||||
# starting a challenge
|
for challenge in self.browse(cr, uid, ids, context=context):
|
||||||
if not vals.get('autojoin_group_id'):
|
user_ids = self._get_challenger_users(cr, uid, challenge.user_domain, context=context)
|
||||||
# starting challenge, add users in autojoin group
|
write_op = [(4, user_id) for user_id in user_ids]
|
||||||
if not vals.get('user_ids'):
|
self.write(cr, uid, [challenge.id], {'user_ids': write_op}, context=context)
|
||||||
vals['user_ids'] = []
|
self.message_subscribe_users(cr, uid, [challenge.id], user_ids, context=context)
|
||||||
for challenge in self.browse(cr, uid, ids, context=context):
|
|
||||||
if challenge.autojoin_group_id:
|
|
||||||
vals['user_ids'] += [(4, user.id) for user in challenge.autojoin_group_id.users]
|
|
||||||
|
|
||||||
self.generate_goals_from_challenge(cr, uid, ids, context=context)
|
self.generate_goals_from_challenge(cr, uid, ids, context=context)
|
||||||
|
|
||||||
|
@ -264,11 +259,6 @@ class gamification_challenge(osv.Model):
|
||||||
|
|
||||||
write_res = super(gamification_challenge, self).write(cr, uid, ids, vals, context=context)
|
write_res = super(gamification_challenge, self).write(cr, uid, ids, vals, context=context)
|
||||||
|
|
||||||
# subscribe new users to the challenge
|
|
||||||
if vals.get('user_ids'):
|
|
||||||
# done with browse after super if changes in groups
|
|
||||||
for challenge in self.browse(cr, uid, ids, context=context):
|
|
||||||
self.message_subscribe_users(cr, uid, [challenge.id], [user.id for user in challenge.user_ids], context=context)
|
|
||||||
|
|
||||||
return write_res
|
return write_res
|
||||||
|
|
||||||
|
@ -325,9 +315,16 @@ class gamification_challenge(osv.Model):
|
||||||
goal_obj.update(cr, uid, goal_ids, context=context)
|
goal_obj.update(cr, uid, goal_ids, context=context)
|
||||||
|
|
||||||
for challenge in self.browse(cr, uid, ids, context=context):
|
for challenge in self.browse(cr, uid, ids, context=context):
|
||||||
if challenge.autojoin_group_id:
|
# in case of new users matching the domain
|
||||||
# check in case of new users in challenge, this happens if manager removed users in challenge manually
|
old_user_ids = [user.id for user in challenge.user_ids]
|
||||||
self.write(cr, uid, [challenge.id], {'user_ids': [(4, user.id) for user in challenge.autojoin_group_id.users]}, context=context)
|
new_user_ids = self._get_challenger_users(cr, uid, challenge.user_domain, context=context)
|
||||||
|
to_remove_ids = list(set(old_user_ids) - set(new_user_ids))
|
||||||
|
to_add_ids = list(set(new_user_ids) - set(old_user_ids))
|
||||||
|
|
||||||
|
write_op = [(3, user_id) for user_id in to_remove_ids]
|
||||||
|
write_op += [(4, user_id) for user_id in to_add_ids]
|
||||||
|
self.write(cr, uid, [challenge.id], {'user_ids': write_op}, context=context)
|
||||||
|
|
||||||
self.generate_goals_from_challenge(cr, uid, [challenge.id], context=context)
|
self.generate_goals_from_challenge(cr, uid, [challenge.id], context=context)
|
||||||
|
|
||||||
# goals closed but still opened at the last report date
|
# goals closed but still opened at the last report date
|
||||||
|
|
|
@ -287,30 +287,33 @@ class gamification_goal(osv.Model):
|
||||||
field_date_name = definition.field_date_id and definition.field_date_id.name or False
|
field_date_name = definition.field_date_id and definition.field_date_id.name or False
|
||||||
|
|
||||||
if definition.computation_mode == 'count' and definition.batch_mode:
|
if definition.computation_mode == 'count' and definition.batch_mode:
|
||||||
|
# batch mode, trying to do as much as possible in one request
|
||||||
general_domain = safe_eval(definition.domain)
|
general_domain = safe_eval(definition.domain)
|
||||||
# goal_distinct_values = {goal.id: safe_eval(definition.batch_user_expression, {'user': goal.user_id}) for goal in goals}
|
|
||||||
field_name = definition.batch_distinctive_field.name
|
field_name = definition.batch_distinctive_field.name
|
||||||
# general_domain.append((field_name, 'in', list(set(goal_distinct_values.keys()))))
|
|
||||||
subqueries = {}
|
subqueries = {}
|
||||||
for goal in goals:
|
for goal in goals:
|
||||||
start_date = field_date_name and goal.start_date or False
|
start_date = field_date_name and goal.start_date or False
|
||||||
end_date = field_date_name and goal.end_date or False
|
end_date = field_date_name and goal.end_date or False
|
||||||
subqueries.setdefault((start_date, end_date), {}).update({goal.id:safe_eval(definition.batch_user_expression, {'user': goal.user_id})})
|
subqueries.setdefault((start_date, end_date), {}).update({goal.id:safe_eval(definition.batch_user_expression, {'user': goal.user_id})})
|
||||||
|
|
||||||
|
# the global query should be split by time periods (especially for recurrent goals)
|
||||||
for (start_date, end_date), query_goals in subqueries.items():
|
for (start_date, end_date), query_goals in subqueries.items():
|
||||||
subquery_domain = list(general_domain)
|
subquery_domain = list(general_domain)
|
||||||
subquery_domain.append((field_name, 'in', list(set(query_goals.values()))))
|
subquery_domain.append((field_name, 'in', list(set(query_goals.values()))))
|
||||||
if start_date:
|
if start_date:
|
||||||
subquery_domain.append((field_date_name, '>=', start_date))
|
subquery_domain.append((field_date_name, '>=', start_date))
|
||||||
if end_date:
|
if end_date:
|
||||||
subquery_domain.append((field_date_name, '>=', end_date))
|
subquery_domain.append((field_date_name, '<=', end_date))
|
||||||
|
|
||||||
user_values = obj.read_group(cr, uid, subquery_domain, fields=[field_name], groupby=[field_name], context=context)
|
|
||||||
|
|
||||||
|
if field_name == 'id':
|
||||||
|
# grouping on id does not work and is similar to search anyway
|
||||||
|
user_ids = obj.search(cr, uid, subquery_domain, context=context)
|
||||||
|
user_values = [{'id': user_id, 'id_count': 1} for user_id in user_ids]
|
||||||
|
else:
|
||||||
|
user_values = obj.read_group(cr, uid, subquery_domain, fields=[field_name], groupby=[field_name], context=context)
|
||||||
|
# user_values has format of read_group: [{'partner_id': 42, 'partner_id_count': 3},...]
|
||||||
for goal in [g for g in goals if g.id in query_goals.keys()]:
|
for goal in [g for g in goals if g.id in query_goals.keys()]:
|
||||||
for user_value in user_values:
|
for user_value in user_values:
|
||||||
# return format of read_group: [{'partner_id': 42, 'partner_id_count': 3},...]
|
|
||||||
queried_value = field_name in user_value and user_value[field_name] or False
|
queried_value = field_name in user_value and user_value[field_name] or False
|
||||||
if isinstance(queried_value, tuple) and len(queried_value) == 2 and isinstance(queried_value[0], (int, long)):
|
if isinstance(queried_value, tuple) and len(queried_value) == 2 and isinstance(queried_value[0], (int, long)):
|
||||||
queried_value = queried_value[0]
|
queried_value = queried_value[0]
|
||||||
|
|
|
@ -31,43 +31,12 @@ class res_users_gamification_group(osv.Model):
|
||||||
_name = 'res.users'
|
_name = 'res.users'
|
||||||
_inherit = ['res.users']
|
_inherit = ['res.users']
|
||||||
|
|
||||||
def write(self, cr, uid, ids, vals, context=None):
|
def get_serialised_gamification_summary(self, cr, uid, excluded_categories=None, context=None):
|
||||||
"""Overwrite to autosubscribe users if added to a group marked as autojoin, user will be added to challenge"""
|
return self._serialised_goals_summary(cr, uid, user_id=uid, excluded_categories=excluded_categories, context=context)
|
||||||
write_res = super(res_users_gamification_group, self).write(cr, uid, ids, vals, context=context)
|
|
||||||
if vals.get('groups_id'):
|
|
||||||
# form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
|
|
||||||
user_group_ids = [command[1] for command in vals['groups_id'] if command[0] == 4]
|
|
||||||
user_group_ids += [id for command in vals['groups_id'] if command[0] == 6 for id in command[2]]
|
|
||||||
|
|
||||||
challenge_obj = self.pool.get('gamification.challenge')
|
def _serialised_goals_summary(self, cr, uid, user_id, excluded_categories=None, context=None):
|
||||||
challenge_ids = challenge_obj.search(cr, SUPERUSER_ID, [('autojoin_group_id', 'in', user_group_ids)], context=context)
|
|
||||||
if challenge_ids:
|
|
||||||
challenge_obj.write(cr, SUPERUSER_ID, challenge_ids, {'user_ids': [(4, user_id) for user_id in ids]}, context=context)
|
|
||||||
challenge_obj.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context)
|
|
||||||
return write_res
|
|
||||||
|
|
||||||
def create(self, cr, uid, vals, context=None):
|
|
||||||
"""Overwrite to autosubscribe users if added to a group marked as autojoin, user will be added to challenge"""
|
|
||||||
write_res = super(res_users_gamification_group, self).create(cr, uid, vals, context=context)
|
|
||||||
if vals.get('groups_id'):
|
|
||||||
# form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
|
|
||||||
user_group_ids = [command[1] for command in vals['groups_id'] if command[0] == 4]
|
|
||||||
user_group_ids += [id for command in vals['groups_id'] if command[0] == 6 for id in command[2]]
|
|
||||||
|
|
||||||
challenge_obj = self.pool.get('gamification.challenge')
|
|
||||||
challenge_ids = challenge_obj.search(cr, SUPERUSER_ID, [('autojoin_group_id', 'in', user_group_ids)], context=context)
|
|
||||||
if challenge_ids:
|
|
||||||
challenge_obj.write(cr, SUPERUSER_ID, challenge_ids, {'user_ids': [(4, write_res)]}, context=context)
|
|
||||||
challenge_obj.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context)
|
|
||||||
return write_res
|
|
||||||
|
|
||||||
# def get_goals_todo_info(self, cr, uid, context=None):
|
|
||||||
|
|
||||||
def get_serialised_gamification_summary(self, cr, uid, context=None):
|
|
||||||
return self._serialised_goals_summary(cr, uid, user_id=uid, context=context)
|
|
||||||
|
|
||||||
def _serialised_goals_summary(self, cr, uid, user_id, context=None):
|
|
||||||
"""Return a serialised list of goals assigned to the user, grouped by challenge
|
"""Return a serialised list of goals assigned to the user, grouped by challenge
|
||||||
|
:excluded_categories: list of challenge categories to exclude in search
|
||||||
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
@ -81,9 +50,11 @@ class res_users_gamification_group(osv.Model):
|
||||||
"""
|
"""
|
||||||
all_goals_info = []
|
all_goals_info = []
|
||||||
challenge_obj = self.pool.get('gamification.challenge')
|
challenge_obj = self.pool.get('gamification.challenge')
|
||||||
|
domain = [('user_ids', 'in', uid), ('state', '=', 'inprogress')]
|
||||||
|
if excluded_categories and isinstance(excluded_categories, list):
|
||||||
|
domain.append(('category', 'not in', excluded_categories))
|
||||||
user = self.browse(cr, uid, uid, context=context)
|
user = self.browse(cr, uid, uid, context=context)
|
||||||
challenge_ids = challenge_obj.search(cr, uid, [('user_ids', 'in', uid), ('state', '=', 'inprogress')], context=context)
|
challenge_ids = challenge_obj.search(cr, uid, domain, context=context)
|
||||||
for challenge in challenge_obj.browse(cr, uid, challenge_ids, context=context):
|
for challenge in challenge_obj.browse(cr, uid, challenge_ids, context=context):
|
||||||
# serialize goals info to be able to use it in javascript
|
# serialize goals info to be able to use it in javascript
|
||||||
lines = challenge_obj._get_serialized_challenge_lines(cr, uid, challenge, user_id, restrict_top=MAX_VISIBILITY_RANKING, context=context)
|
lines = challenge_obj._get_serialized_challenge_lines(cr, uid, challenge, user_id, restrict_top=MAX_VISIBILITY_RANKING, context=context)
|
||||||
|
@ -111,28 +82,3 @@ class res_users_gamification_group(osv.Model):
|
||||||
}
|
}
|
||||||
challenge_info.append(values)
|
challenge_info.append(values)
|
||||||
return challenge_info
|
return challenge_info
|
||||||
|
|
||||||
|
|
||||||
class res_groups_gamification_group(osv.Model):
|
|
||||||
""" Update of res.groups class
|
|
||||||
- if adding users from a group, check gamification.challenge linked to
|
|
||||||
this group, and the user. This is done by overriding the write method.
|
|
||||||
"""
|
|
||||||
_name = 'res.groups'
|
|
||||||
_inherit = 'res.groups'
|
|
||||||
|
|
||||||
# No need to overwrite create as very unlikely to be the value in the autojoin_group_id field
|
|
||||||
def write(self, cr, uid, ids, vals, context=None):
|
|
||||||
"""Overwrite to autosubscribe users if add users to a group marked as autojoin, these will be added to the challenge"""
|
|
||||||
write_res = super(res_groups_gamification_group, self).write(cr, uid, ids, vals, context=context)
|
|
||||||
if vals.get('users'):
|
|
||||||
# form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
|
|
||||||
user_ids = [command[1] for command in vals['users'] if command[0] == 4]
|
|
||||||
user_ids += [id for command in vals['users'] if command[0] == 6 for id in command[2]]
|
|
||||||
|
|
||||||
challenge_obj = self.pool.get('gamification.challenge')
|
|
||||||
challenge_ids = challenge_obj.search(cr, SUPERUSER_ID, [('autojoin_group_id', 'in', ids)], context=context)
|
|
||||||
if challenge_ids:
|
|
||||||
challenge_obj.write(cr, SUPERUSER_ID, challenge_ids, {'user_ids': [(4, user_id) for user_id in user_ids]}, context=context)
|
|
||||||
challenge_obj.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context)
|
|
||||||
return write_res
|
|
||||||
|
|
|
@ -57,6 +57,7 @@ class test_challenge(common.TransactionCase):
|
||||||
'groups_id': [(6, 0, [self.group_user_id])]
|
'groups_id': [(6, 0, [self.group_user_id])]
|
||||||
}, {'no_reset_password': True})
|
}, {'no_reset_password': True})
|
||||||
|
|
||||||
|
self.challenge_obj._update_all(cr, uid, [self.challenge_base_id], context=context)
|
||||||
challenge = self.challenge_obj.browse(cr, uid, self.challenge_base_id, context=context)
|
challenge = self.challenge_obj.browse(cr, uid, self.challenge_base_id, context=context)
|
||||||
self.assertGreaterEqual(len(challenge.user_ids), len(user_ids)+1, "These are not droids you are looking for")
|
self.assertGreaterEqual(len(challenge.user_ids), len(user_ids)+1, "These are not droids you are looking for")
|
||||||
|
|
||||||
|
|
|
@ -45,9 +45,9 @@
|
||||||
<h1>
|
<h1>
|
||||||
<field name="name" placeholder="e.g. Monthly Sales Objectives"/>
|
<field name="name" placeholder="e.g. Monthly Sales Objectives"/>
|
||||||
</h1>
|
</h1>
|
||||||
<label for="user_ids" class="oe_edit_only" string="Assign Challenge To"/>
|
<label for="user_domain" class="oe_edit_only" string="Assign Challenge To"/>
|
||||||
<div>
|
<div>
|
||||||
<field name="user_ids" widget="many2many_tags" />
|
<field name="user_domain" widget="char_domain" options="{'model': 'res.users'}" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -87,12 +87,12 @@
|
||||||
</page>
|
</page>
|
||||||
<page string="Reward">
|
<page string="Reward">
|
||||||
<group>
|
<group>
|
||||||
<field name="reward_id"/>
|
<field name="reward_id" attrs="{'required': [('reward_realtime','=', True)]}" />
|
||||||
<field name="reward_first_id" />
|
<field name="reward_first_id" />
|
||||||
<field name="reward_second_id" attrs="{'invisible': [('reward_first_id','=', False)]}" />
|
<field name="reward_second_id" attrs="{'invisible': [('reward_first_id','=', False)]}" />
|
||||||
<field name="reward_third_id" attrs="{'invisible': ['|',('reward_first_id','=', False),('reward_second_id','=', False)]}" />
|
<field name="reward_third_id" attrs="{'invisible': ['|',('reward_first_id','=', False),('reward_second_id','=', False)]}" />
|
||||||
<field name="reward_failure" attrs="{'invisible': [('reward_first_id','=', False)]}" />
|
<field name="reward_failure" attrs="{'invisible': [('reward_first_id','=', False)]}" />
|
||||||
<field name="reward_realtime" attrs="{'readonly': [('reward_id','=', False)], 'required': [('reward_id','!=', False)]}" />
|
<field name="reward_realtime" />
|
||||||
</group>
|
</group>
|
||||||
<div class="oe_grey">
|
<div class="oe_grey">
|
||||||
<p>Badges are granted when a challenge is finished. This is either at the end of a running period (eg: end of the month for a monthly challenge), at the end date of a challenge (if no periodicity is set) or when the challenge is manually closed.</p>
|
<p>Badges are granted when a challenge is finished. This is either at the end of a running period (eg: end of the month for a monthly challenge), at the end date of a challenge (if no periodicity is set) or when the challenge is manually closed.</p>
|
||||||
|
@ -100,7 +100,6 @@
|
||||||
</page>
|
</page>
|
||||||
<page string="Advanced Options">
|
<page string="Advanced Options">
|
||||||
<group string="Subscriptions">
|
<group string="Subscriptions">
|
||||||
<field name="autojoin_group_id" />
|
|
||||||
<field name="invited_user_ids" widget="many2many_tags" />
|
<field name="invited_user_ids" widget="many2many_tags" />
|
||||||
</group>
|
</group>
|
||||||
<group string="Notification Messages">
|
<group string="Notification Messages">
|
||||||
|
|
|
@ -130,7 +130,7 @@
|
||||||
<field name="name">Monthly Sales Targets</field>
|
<field name="name">Monthly Sales Targets</field>
|
||||||
<field name="period">monthly</field>
|
<field name="period">monthly</field>
|
||||||
<field name="visibility_mode">ranking</field>
|
<field name="visibility_mode">ranking</field>
|
||||||
<field name="autojoin_group_id" eval="ref('base.group_sale_salesman')" />
|
<field name="user_domain">[('groups_id', 'in', ref('base.group_sale_salesman'))]</field>
|
||||||
<field name="report_message_frequency">weekly</field>
|
<field name="report_message_frequency">weekly</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@
|
||||||
<field name="name">Lead Acquisition</field>
|
<field name="name">Lead Acquisition</field>
|
||||||
<field name="period">monthly</field>
|
<field name="period">monthly</field>
|
||||||
<field name="visibility_mode">ranking</field>
|
<field name="visibility_mode">ranking</field>
|
||||||
<field name="autojoin_group_id" eval="ref('base.group_sale_salesman')" />
|
<field name="user_domain">[('groups_id', 'in', ref('base.group_sale_salesman'))]</field>
|
||||||
<field name="report_message_frequency">weekly</field>
|
<field name="report_message_frequency">weekly</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ actions(Sign in/Sign out) performed by them.
|
||||||
""",
|
""",
|
||||||
'author': 'OpenERP SA',
|
'author': 'OpenERP SA',
|
||||||
'images': ['images/hr_attendances.jpeg'],
|
'images': ['images/hr_attendances.jpeg'],
|
||||||
'depends': ['hr'],
|
'depends': ['hr', 'report'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/ir_rule.xml',
|
'security/ir_rule.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
|
@ -43,6 +43,7 @@ actions(Sign in/Sign out) performed by them.
|
||||||
'wizard/hr_attendance_byweek_view.xml',
|
'wizard/hr_attendance_byweek_view.xml',
|
||||||
'wizard/hr_attendance_error_view.xml',
|
'wizard/hr_attendance_error_view.xml',
|
||||||
'res_config_view.xml',
|
'res_config_view.xml',
|
||||||
|
'views/report_attendanceerrors.xml',
|
||||||
],
|
],
|
||||||
'demo': ['hr_attendance_demo.xml'],
|
'demo': ['hr_attendance_demo.xml'],
|
||||||
'test': [
|
'test': [
|
||||||
|
@ -51,10 +52,10 @@ actions(Sign in/Sign out) performed by them.
|
||||||
],
|
],
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'auto_install': False,
|
'auto_install': False,
|
||||||
|
|
||||||
#web
|
#web
|
||||||
"js": ["static/src/js/attendance.js"],
|
"js": ["static/src/js/attendance.js"],
|
||||||
'qweb' : ["static/src/xml/attendance.xml"],
|
'qweb': ["static/src/xml/attendance.xml"],
|
||||||
'css' : ["static/src/css/slider.css"],
|
'css': ["static/src/css/slider.css"],
|
||||||
}
|
}
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<openerp>
|
||||||
<data>
|
<data>
|
||||||
|
<report
|
||||||
<report auto="False" id="attendance_error_report" keyword="client_print_multi" menu="False" model="hr.employee" multi="True" name="report.hr.timesheet.attendance.error" rml="hr_attendance/report/attendance_errors.rml" string="Attendance Error Report"/>
|
id="action_report_hrattendanceerror"
|
||||||
|
model="hr.employee"
|
||||||
|
string="Attendance Error Report"
|
||||||
|
report_type="qweb-pdf"
|
||||||
|
name="hr_attendance.report_attendanceerrors"
|
||||||
|
file="hr_attendance.report_attendanceerrors"
|
||||||
|
/>
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -21,9 +21,10 @@
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
|
from openerp.osv import osv
|
||||||
from openerp.report import report_sxw
|
from openerp.report import report_sxw
|
||||||
|
|
||||||
|
|
||||||
class attendance_print(report_sxw.rml_parse):
|
class attendance_print(report_sxw.rml_parse):
|
||||||
|
|
||||||
def __init__(self, cr, uid, name, context):
|
def __init__(self, cr, uid, name, context):
|
||||||
|
@ -39,7 +40,6 @@ class attendance_print(report_sxw.rml_parse):
|
||||||
emp_obj_list = self.pool.get('hr.employee').browse(self.cr, self.uid, emp_ids)
|
emp_obj_list = self.pool.get('hr.employee').browse(self.cr, self.uid, emp_ids)
|
||||||
return emp_obj_list
|
return emp_obj_list
|
||||||
|
|
||||||
|
|
||||||
def _lst(self, employee_id, dt_from, dt_to, max, *args):
|
def _lst(self, employee_id, dt_from, dt_to, max, *args):
|
||||||
self.cr.execute("select name as date, create_date, action, create_date-name as delay from hr_attendance where employee_id=%s and to_char(name,'YYYY-mm-dd')<=%s and to_char(name,'YYYY-mm-dd')>=%s and action IN (%s,%s) order by name", (employee_id, dt_to, dt_from, 'sign_in', 'sign_out'))
|
self.cr.execute("select name as date, create_date, action, create_date-name as delay from hr_attendance where employee_id=%s and to_char(name,'YYYY-mm-dd')<=%s and to_char(name,'YYYY-mm-dd')>=%s and action IN (%s,%s) order by name", (employee_id, dt_to, dt_from, 'sign_in', 'sign_out'))
|
||||||
res = self.cr.dictfetchall()
|
res = self.cr.dictfetchall()
|
||||||
|
@ -75,7 +75,11 @@ class attendance_print(report_sxw.rml_parse):
|
||||||
}
|
}
|
||||||
return [result_dict]
|
return [result_dict]
|
||||||
|
|
||||||
report_sxw.report_sxw('report.hr.attendance.error', 'hr.employee', 'addons/hr_attendance/report/attendance_errors.rml', parser=attendance_print, header='internal')
|
|
||||||
|
class report_hr_attendanceerrors(osv.AbstractModel):
|
||||||
|
_name = 'report.hr_attendance.report_attendanceerrors'
|
||||||
|
_inherit = 'report.abstract_report'
|
||||||
|
_template = 'hr_attendance.report_attendanceerrors'
|
||||||
|
_wrapped_report_class = attendance_print
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
||||||
|
|
|
@ -1,137 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<document filename="Attendance Errors.pdf">
|
|
||||||
<template title="Attendance Errors" author="OpenERP S.A.(sales@openerp.com)" allowSplitting="20">
|
|
||||||
<pageTemplate id="first">
|
|
||||||
<frame id="first" x1="28.0" y1="26.0" width="536" height="784"/>
|
|
||||||
</pageTemplate>
|
|
||||||
</template>
|
|
||||||
<stylesheet>
|
|
||||||
<blockTableStyle id="Standard_Outline">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table1">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="4,-1" stop="4,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table2">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="3,-1" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="4,-1" stop="4,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table3">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="2,0" stop="2,0"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<initialize>
|
|
||||||
<paraStyle name="all" alignment="justify"/>
|
|
||||||
</initialize>
|
|
||||||
<paraStyle name="Standard" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Text body" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Heading" fontName="Helvetica" fontSize="14.0" leading="17" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="List" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Table Contents" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Caption" fontName="Helvetica" fontSize="12.0" leading="15" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Index" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="terp_header" fontName="Helvetica-Bold" fontSize="12.0" leading="15" alignment="LEFT" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_default_8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_9" fontName="Helvetica" fontSize="9.0" leading="11" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_header_Centre" fontName="Helvetica-Bold" fontSize="14.0" leading="17" alignment="CENTER" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="LEFT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details_Centre" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="CENTER" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_default_Centre_8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Centre_9" fontName="Helvetica" fontSize="9.0" leading="11" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Bold_8" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Bold_9" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Centre_9_Bold" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="Footer" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Table Heading" fontName="Helvetica" alignment="CENTER"/>
|
|
||||||
<paraStyle name="Horizontal Line" fontName="Helvetica" fontSize="6.0" leading="8" spaceBefore="0.0" spaceAfter="14.0"/>
|
|
||||||
<paraStyle name="Heading 9" fontName="Helvetica-Bold" fontSize="75%" leading="NaN" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_General" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_General_Centre" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_General_Right" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details_Right" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="RIGHT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_default_Right_8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_header_Right" fontName="Helvetica-Bold" fontSize="15.0" leading="19" alignment="LEFT" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_default_address" fontName="Helvetica" fontSize="10.0" leading="13" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Right_9" fontName="Helvetica" fontSize="9.0" leading="11" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<images/>
|
|
||||||
</stylesheet>
|
|
||||||
<story>
|
|
||||||
<para style="terp_default_8">[[ repeatIn(get_employees(data['form']['emp_ids']),'employee') ]]</para>
|
|
||||||
<para style="terp_header_Centre">Attendance Errors</para>
|
|
||||||
<para style="terp_tblheader_Details">[[ employee.name ]]</para>
|
|
||||||
<blockTable colWidths="107.0,107.0,107.0,107.0,107.0" style="Table1">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details">Operation</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Centre">Date Signed</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Centre">Date Recorded</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Centre">Delay</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Centre">Min Delay</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<section>
|
|
||||||
<para style="terp_default_8">[[ repeatIn(lst(employee.id,data['form']['init_date'], data['form']['end_date'], data['form']['max_delay']), 'att') ]]</para>
|
|
||||||
<blockTable colWidths="107.0,107.0,107.0,107.0,107.0" style="Table2">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_9">[[ att['action'] ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Centre_9">[[ formatLang(att['date'],date_time=True) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Centre_9">[[ formatLang(att['create_date'],date_time=True) ]] </para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Centre_9">[[ att['delay'] ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Centre_9">[[ att['delay2'] ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
</section>
|
|
||||||
<blockTable colWidths="322.0,108.0,107.0" style="Table3">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Bold_9">Total period:[[ repeatIn(total(employee.id,data['form']['init_date'], data['form']['end_date'], data['form']['max_delay']),'total') ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Centre_9_Bold">[[ total['total'] ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Centre_9_Bold">[[ total['total2'] ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="terp_default_8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="terp_default_9">(*) A positive delay means that the employee worked less than recorded.</para>
|
|
||||||
<para style="terp_default_9">(*) A negative delay means that the employee worked more than encoded.</para>
|
|
||||||
</story>
|
|
||||||
</document>
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
<template id="report_attendanceerrors">
|
||||||
|
<t t-call="report.html_container">
|
||||||
|
<t t-foreach="get_employees(data['form']['emp_ids'])" t-as="employee">
|
||||||
|
<t t-call="report.internal_layout">
|
||||||
|
<div class="page">
|
||||||
|
<div class="oe_structure"/>
|
||||||
|
<h2>Attendance Errors: <span t-esc="employee.name"/></h2>
|
||||||
|
|
||||||
|
<table class="table table-condensed mt32">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Operation</th>
|
||||||
|
<th>Date Signed</th>
|
||||||
|
<th>Date Recorded</th>
|
||||||
|
<th>Delay</th>
|
||||||
|
<th>Min Delay</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="lst(employee.id,data['form']['init_date'], data['form']['end_date'], data['form']['max_delay'])" t-as="att">
|
||||||
|
<td><span t-esc="att['action']"/></td>
|
||||||
|
<td><span t-esc="formatLang(att['date'],date_time=True)"/></td>
|
||||||
|
<td><span t-esc="formatLang(att['create_date'],date_time=True)"/></td>
|
||||||
|
<td><span t-esc="att['delay']"/></td>
|
||||||
|
<td><span t-esc="att['delay2']"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="border-black" t-foreach="total(employee.id,data['form']['init_date'], data['form']['end_date'], data['form']['max_delay'])" t-as="total">
|
||||||
|
<td colspan="3"><strong>Total period</strong></td>
|
||||||
|
<td><strong t-esc="total['total']"/></td>
|
||||||
|
<td><strong t-esc="total['total2']"/></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p>(*) A positive delay means that the employee worked less than recorded.<br/>
|
||||||
|
(*) A negative delay means that the employee worked more than encoded.</p>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -23,6 +23,7 @@ import time
|
||||||
from openerp.osv import fields, osv
|
from openerp.osv import fields, osv
|
||||||
from openerp.tools.translate import _
|
from openerp.tools.translate import _
|
||||||
|
|
||||||
|
|
||||||
class hr_attendance_error(osv.osv_memory):
|
class hr_attendance_error(osv.osv_memory):
|
||||||
|
|
||||||
_name = 'hr.attendance.error'
|
_name = 'hr.attendance.error'
|
||||||
|
@ -58,11 +59,8 @@ class hr_attendance_error(osv.osv_memory):
|
||||||
'model': 'hr.employee',
|
'model': 'hr.employee',
|
||||||
'form': data_error
|
'form': data_error
|
||||||
}
|
}
|
||||||
return {
|
return self.pool['report'].get_action(
|
||||||
'type': 'ir.actions.report.xml',
|
cr, uid, [], 'hr_attendance.report_attendanceerrors', data=datas, context=context
|
||||||
'report_name': 'hr.attendance.error',
|
)
|
||||||
'datas': datas,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -46,7 +46,7 @@ This module also uses analytic accounting and is compatible with the invoice on
|
||||||
'author': 'OpenERP SA',
|
'author': 'OpenERP SA',
|
||||||
'website': 'http://www.openerp.com',
|
'website': 'http://www.openerp.com',
|
||||||
'images': ['images/hr_expenses_analysis.jpeg', 'images/hr_expenses.jpeg'],
|
'images': ['images/hr_expenses_analysis.jpeg', 'images/hr_expenses.jpeg'],
|
||||||
'depends': ['hr', 'account_accountant'],
|
'depends': ['hr', 'account_accountant', 'report'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'hr_expense_data.xml',
|
'hr_expense_data.xml',
|
||||||
|
@ -59,6 +59,7 @@ This module also uses analytic accounting and is compatible with the invoice on
|
||||||
'report/hr_expense_report_view.xml',
|
'report/hr_expense_report_view.xml',
|
||||||
'board_hr_expense_view.xml',
|
'board_hr_expense_view.xml',
|
||||||
'hr_expense_installer_view.xml',
|
'hr_expense_installer_view.xml',
|
||||||
|
'views/report_expense.xml',
|
||||||
],
|
],
|
||||||
'demo': ['hr_expense_demo.xml'],
|
'demo': ['hr_expense_demo.xml'],
|
||||||
'test': [
|
'test': [
|
||||||
|
@ -69,4 +70,5 @@ This module also uses analytic accounting and is compatible with the invoice on
|
||||||
'auto_install': False,
|
'auto_install': False,
|
||||||
'application': True,
|
'application': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<openerp>
|
||||||
<data>
|
<data>
|
||||||
|
<report
|
||||||
<report auto="False" id="hr_expenses" model="hr.expense.expense" name="hr.expense" rml="hr_expense/report/expense.rml" string="HR expenses"/>
|
id="action_report_hr_expense"
|
||||||
|
string="HR Expense"
|
||||||
|
model="hr.expense.expense"
|
||||||
|
report_type="qweb-pdf"
|
||||||
|
name="hr_expense.report_expense"
|
||||||
|
file="hr_expense.report_expense"
|
||||||
|
/>
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -19,8 +19,6 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
import expense
|
|
||||||
import hr_expense_report
|
import hr_expense_report
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
||||||
|
|
|
@ -1,302 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<document filename="test.pdf">
|
|
||||||
<template title="Expenses" author="OpenERP S.A. (sales@openerp.com)" allowSplitting="20">
|
|
||||||
<pageTemplate id="first">
|
|
||||||
<frame id="first" x1="16.0" y1="57.0" width="522" height="728"/>
|
|
||||||
</pageTemplate>
|
|
||||||
</template>
|
|
||||||
<stylesheet>
|
|
||||||
<blockTableStyle id="Standard_Outline">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table_employee_ref_header">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#e6e6e6" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#e6e6e6" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="3,0" stop="3,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="3,-1" stop="3,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table_employee_ref_content">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#e6e6e6" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#e6e6e6" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="3,0" stop="3,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="3,-1" stop="3,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table_hr_expense_line_header">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="4,-1" stop="4,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="5,-1" stop="5,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table_expense_line">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="3,-1" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="4,-1" stop="4,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="5,-1" stop="5,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="3,-1" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="4,-1" stop="4,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="5,-1" stop="5,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table_Final_total">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#ffffff" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="2,0" stop="2,0"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<initialize>
|
|
||||||
<paraStyle name="all" alignment="justify"/>
|
|
||||||
</initialize>
|
|
||||||
<paraStyle name="Standard" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Text body" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Heading" fontName="Helvetica" fontSize="14.0" leading="17" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="List" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Table Contents" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Table Heading" fontName="Helvetica" alignment="CENTER"/>
|
|
||||||
<paraStyle name="Caption" fontName="Helvetica" fontSize="12.0" leading="15" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Index" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Footer" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Horizontal Line" fontName="Helvetica" fontSize="6.0" leading="8" spaceBefore="0.0" spaceAfter="14.0"/>
|
|
||||||
<paraStyle name="terp_header" fontName="Helvetica-Bold" fontSize="12.0" leading="15" alignment="LEFT" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Heading 9" fontName="Helvetica-Bold" fontSize="75%" leading="NaN" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_General" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="LEFT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_default_8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Bold_8" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_General_Centre" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_General_Right" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details_Centre" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="CENTER" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details_Right" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="RIGHT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_default_Right_8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Centre_8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_header_Right" fontName="Helvetica-Bold" fontSize="15.0" leading="19" alignment="LEFT" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_header_Centre" fontName="Helvetica-Bold" fontSize="12.0" leading="15" alignment="CENTER" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_default_address" fontName="Helvetica" fontSize="10.0" leading="13" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_9" fontName="Helvetica" fontSize="9.0" leading="11" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Bold_centre_9" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Centre_9" fontName="Helvetica" fontSize="9.0" leading="11" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Right_9" fontName="Helvetica" fontSize="9.0" leading="11" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_1" fontName="Helvetica" fontSize="2.0" leading="3" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_9_bold_right" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_italic_8" fontName="Helvetica-Oblique" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<images/>
|
|
||||||
</stylesheet>
|
|
||||||
<story>
|
|
||||||
<pto>
|
|
||||||
<pto_header>
|
|
||||||
<blockTable colWidths="60.0,200.0,50.0,70.0,70.0,85.0" style="Table_hr_expense_line_header">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details">Date</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details">Name</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details">Ref.</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Right">Unit Price</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Right">Qty</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Right">Price</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
</pto_header>
|
|
||||||
<para style="terp_default_8">[[ repeatIn(objects,'o') ]]</para>
|
|
||||||
<para style="terp_default_8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="terp_header_Centre">HR Expenses</para>
|
|
||||||
<para style="terp_tblheader_General_Centre">
|
|
||||||
<font face="Helvetica" size="9.0">[[ o.name or '' ]]</font>
|
|
||||||
</para>
|
|
||||||
<para style="terp_default_8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<blockTable colWidths="130.5,130.5,130.5,130.5" style="Table_employee_ref_header">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_General_Centre">Employee</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_General_Centre">Date</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_General_Centre">Description</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_General_Centre">Validated By</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="130.5,130.5,130.5,130.5" style="Table_employee_ref_content">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Centre_8">[[ o.employee_id.name ]] </para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Centre_8">[[ formatLang(o.date,date=True) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Centre_8">[[ o.name ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Centre_8">[[ o.user_valid.name ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="terp_default_8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<blockTable colWidths="60.0,200.0,50.0,70.0,70.0,85.0" style="Table_hr_expense_line_header">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details">Date</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details">Name</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details">Ref.</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Right">Unit Price</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Right">Qty</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Right">Price</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="terp_default_1">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<section>
|
|
||||||
<para style="terp_default_8">[[ repeatIn(o.line_ids,'line') ]]</para>
|
|
||||||
<blockTable colWidths="60.0,200.0,50.0,70.0,70.0,85.0" style="Table_expense_line">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_9">[[ formatLang(line.date_value,date=True) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_9">[[ line.name or '' ]] [[ line.description or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_9">[[ line.ref or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9">[[ formatLang(line.unit_amount) ]] </para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9">[[ formatLang(line.unit_quantity) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9">[[ formatLang(line.total_amount, currency_obj=o.currency_id) ]] </para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_italic_8">[[ line.analytic_account and line.analytic_account.complete_name or removeParentNode('tr') ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
</section>
|
|
||||||
<blockTable colWidths="365.0,70.0,100.0" style="Table_Final_total">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details">Total:</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Right">[[ formatLang(o.amount, currency_obj=o.currency_id) ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="terp_default_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="terp_default_9">[[ o.note or '' ]]</para>
|
|
||||||
<para style="terp_default_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="terp_default_9">Certified honest and conform,</para>
|
|
||||||
<para style="terp_default_9">(Date and signature)</para>
|
|
||||||
<para style="terp_default_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="terp_default_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="terp_default_9">This document must be dated and signed for reimbursement</para>
|
|
||||||
</pto>
|
|
||||||
</story>
|
|
||||||
</document>
|
|
|
@ -32,11 +32,3 @@
|
||||||
!python {model: hr.expense.expense}: |
|
!python {model: hr.expense.expense}: |
|
||||||
duplicate_id = self.copy(cr, uid, ref('sep_expenses'), context=context)
|
duplicate_id = self.copy(cr, uid, ref('sep_expenses'), context=context)
|
||||||
self.expense_canceled(cr, uid, [duplicate_id])
|
self.expense_canceled(cr, uid, [duplicate_id])
|
||||||
-
|
|
||||||
I print a report of the expenses.
|
|
||||||
-
|
|
||||||
!python {model: hr.expense.expense}: |
|
|
||||||
data, format = self.print_report(cr, uid, [ref('hr_expense.sep_expenses')], 'hr.expense', {}, {})
|
|
||||||
if openerp.tools.config['test_report_directory']:
|
|
||||||
import os
|
|
||||||
file(os.path.join(openerp.tools.config['test_report_directory'], 'hr_expense-report.'+format), 'wb+').write(data)
|
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
<template id="report_expense">
|
||||||
|
<t t-call="report.html_container">
|
||||||
|
<t t-foreach="docs" t-as="o">
|
||||||
|
<t t-call="report.external_layout">
|
||||||
|
<div class="page">
|
||||||
|
<h2>HR Expenses</h2>
|
||||||
|
|
||||||
|
<div class="row mt32 mb32">
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<strong>Employee:</strong>
|
||||||
|
<p t-field="o.employee_id.name"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<strong>Date:</strong>
|
||||||
|
<p t-field="o.date"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<strong>Description:</strong>
|
||||||
|
<p t-field="o.name"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<strong>Validated By:</strong>
|
||||||
|
<p t-field="o.user_valid"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th class="text-center">Ref.</th>
|
||||||
|
<th>Unit Price</th>
|
||||||
|
<th class="text-center">Qty</th>
|
||||||
|
<th class="text-right">Price</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="o.line_ids" t-as="line">
|
||||||
|
<td><span t-field="line.date_value"/></td>
|
||||||
|
<td>
|
||||||
|
<span t-field="line.name"/>
|
||||||
|
<span t-field="line.description"/><br/>
|
||||||
|
<span t-field="line.analytic_account.complete_name"/>
|
||||||
|
</td>
|
||||||
|
<td style="text-center">
|
||||||
|
<span t-field="line.ref"/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span t-field="line.unit_amount"/>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<span t-field="line.unit_quantity"/>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-field="line.total_amount"
|
||||||
|
t-field-options='{"widget": "monetary", "display_currency":"o.currency_id"}'/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-4 pull-right">
|
||||||
|
<table class="table table-condensed">
|
||||||
|
<tr class="border-black">
|
||||||
|
<td><strong>Total</strong></td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-field="o.amount"
|
||||||
|
t-field-options='{"widget": "monetary", "display_currency": "o.currency_id"}'/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p t-field="o.note"/>
|
||||||
|
<p>Certified honest and conform,<br/>(Date and signature).<br/><br/></p>
|
||||||
|
<p>This document must be dated and signed for reimbursement.</p>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -19,6 +19,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Payroll',
|
'name': 'Payroll',
|
||||||
'version': '1.0',
|
'version': '1.0',
|
||||||
|
@ -37,14 +38,20 @@ Generic Payroll system.
|
||||||
* Monthly Payroll Register
|
* Monthly Payroll Register
|
||||||
* Integrated with Holiday Management
|
* Integrated with Holiday Management
|
||||||
""",
|
""",
|
||||||
'author':'OpenERP SA',
|
'author': 'OpenERP SA',
|
||||||
'website':'http://www.openerp.com',
|
'website': 'http://www.openerp.com',
|
||||||
'images': ['images/hr_company_contributions.jpeg','images/hr_salary_heads.jpeg','images/hr_salary_structure.jpeg','images/hr_employee_payslip.jpeg'],
|
'images': [
|
||||||
|
'images/hr_company_contributions.jpeg',
|
||||||
|
'images/hr_salary_heads.jpeg',
|
||||||
|
'images/hr_salary_structure.jpeg',
|
||||||
|
'images/hr_employee_payslip.jpeg'
|
||||||
|
],
|
||||||
'depends': [
|
'depends': [
|
||||||
'hr',
|
'hr',
|
||||||
'hr_contract',
|
'hr_contract',
|
||||||
'hr_holidays',
|
'hr_holidays',
|
||||||
'decimal_precision',
|
'decimal_precision',
|
||||||
|
'report',
|
||||||
],
|
],
|
||||||
'data': [
|
'data': [
|
||||||
'security/hr_security.xml',
|
'security/hr_security.xml',
|
||||||
|
@ -57,12 +64,12 @@ Generic Payroll system.
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'wizard/hr_payroll_contribution_register_report.xml',
|
'wizard/hr_payroll_contribution_register_report.xml',
|
||||||
'res_config_view.xml',
|
'res_config_view.xml',
|
||||||
|
'views/report_contributionregister.xml',
|
||||||
|
'views/report_payslip.xml',
|
||||||
|
'views/report_payslipdetails.xml',
|
||||||
],
|
],
|
||||||
'test': [
|
'test': [
|
||||||
'test/payslip.yml',
|
'test/payslip.yml',
|
||||||
# 'test/payment_advice.yml',
|
|
||||||
# 'test/payroll_register.yml',
|
|
||||||
# 'test/hr_payroll_report.yml',
|
|
||||||
],
|
],
|
||||||
'demo': ['hr_payroll_demo.xml'],
|
'demo': ['hr_payroll_demo.xml'],
|
||||||
'installable': True,
|
'installable': True,
|
||||||
|
|
|
@ -1,31 +1,30 @@
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<openerp>
|
<openerp>
|
||||||
<data>
|
<data>
|
||||||
|
<report
|
||||||
|
id="action_contribution_register"
|
||||||
|
model="hr.contribution.register"
|
||||||
|
string="PaySlip Lines By Conribution Register"
|
||||||
|
report_type="qweb-pdf"
|
||||||
|
name="hr_payroll.report_contributionregister"
|
||||||
|
file="hr_payroll.report_contributionregister"
|
||||||
|
menu="False"
|
||||||
|
/>
|
||||||
|
<report
|
||||||
|
id="action_report_payslip"
|
||||||
|
model="hr.payslip"
|
||||||
|
string="Payslip"
|
||||||
|
report_type="qweb-pdf"
|
||||||
|
name="hr_payroll.report_payslip"
|
||||||
|
file="hr_payroll.report_payslip"
|
||||||
|
/>
|
||||||
<report
|
<report
|
||||||
auto="False"
|
|
||||||
id="payslip_report"
|
|
||||||
model="hr.payslip"
|
|
||||||
name="payslip"
|
|
||||||
rml="hr_payroll/report/report_payslip.rml"
|
|
||||||
string="Employee PaySlip" />
|
|
||||||
|
|
||||||
<report
|
|
||||||
auto="False"
|
|
||||||
id="payslip_details_report"
|
id="payslip_details_report"
|
||||||
model="hr.payslip"
|
model="hr.payslip"
|
||||||
name="paylip.details"
|
string="PaySlip Details"
|
||||||
rml="hr_payroll/report/report_payslip_details.rml"
|
report_type="qweb-pdf"
|
||||||
string="PaySlip Details" />
|
name="hr_payroll.report_payslipdetails"
|
||||||
|
file="hr_payroll.report_payslipdetails"
|
||||||
<report
|
/>
|
||||||
auto="False"
|
|
||||||
menu="False"
|
|
||||||
id="contribution_register"
|
|
||||||
model="hr.contribution.register"
|
|
||||||
name="contribution.register.lines"
|
|
||||||
rml="hr_payroll/report/report_contribution_register.rml"
|
|
||||||
string="PaySlip Lines By Conribution Register" />
|
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -24,9 +24,10 @@
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dateutil import relativedelta
|
from dateutil import relativedelta
|
||||||
|
from openerp.osv import osv
|
||||||
from openerp.report import report_sxw
|
from openerp.report import report_sxw
|
||||||
|
|
||||||
|
|
||||||
class contribution_register_report(report_sxw.rml_parse):
|
class contribution_register_report(report_sxw.rml_parse):
|
||||||
def __init__(self, cr, uid, name, context):
|
def __init__(self, cr, uid, name, context):
|
||||||
super(contribution_register_report, self).__init__(cr, uid, name, context)
|
super(contribution_register_report, self).__init__(cr, uid, name, context)
|
||||||
|
@ -44,7 +45,6 @@ class contribution_register_report(report_sxw.rml_parse):
|
||||||
return self.regi_total
|
return self.regi_total
|
||||||
|
|
||||||
def _get_payslip_lines(self, obj):
|
def _get_payslip_lines(self, obj):
|
||||||
payslip_obj = self.pool.get('hr.payslip')
|
|
||||||
payslip_line = self.pool.get('hr.payslip.line')
|
payslip_line = self.pool.get('hr.payslip.line')
|
||||||
payslip_lines = []
|
payslip_lines = []
|
||||||
res = []
|
res = []
|
||||||
|
@ -69,6 +69,11 @@ class contribution_register_report(report_sxw.rml_parse):
|
||||||
self.regi_total += line.total
|
self.regi_total += line.total
|
||||||
return res
|
return res
|
||||||
|
|
||||||
report_sxw.report_sxw('report.contribution.register.lines', 'hr.contribution.register', 'hr_payroll/report/report_contribution_register.rml', parser=contribution_register_report)
|
|
||||||
|
class wrapped_report_contribution_register(osv.AbstractModel):
|
||||||
|
_name = 'report.hr_payroll.report_contributionregister'
|
||||||
|
_inherit = 'report.abstract_report'
|
||||||
|
_template = 'hr_payroll.report_contributionregister'
|
||||||
|
_wrapped_report_class = contribution_register_report
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -1,234 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<document filename="test.pdf">
|
|
||||||
<template title="Test" author="Martin Simon" allowSplitting="20">
|
|
||||||
<pageTemplate id="first">
|
|
||||||
<frame id="first" x1="28.0" y1="28.0" width="539" height="786"/>
|
|
||||||
</pageTemplate>
|
|
||||||
</template>
|
|
||||||
<stylesheet>
|
|
||||||
<blockTableStyle id="Standard_Outline">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table1">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table3">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="2,-1" stop="2,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table4">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="2,-1" stop="2,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table2">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="4,-1" stop="4,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="5,-1" stop="5,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table16">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="3,-1" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="4,-1" stop="4,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="5,-1" stop="5,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table5">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#000000" start="2,0" stop="2,0"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<initialize>
|
|
||||||
<paraStyle name="all" alignment="justify"/>
|
|
||||||
</initialize>
|
|
||||||
<paraStyle name="P1" rightIndent="-56.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P2" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P3" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P4" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P5" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P6" fontName="Helvetica-Bold" fontSize="14.0" leading="17" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P7" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P8" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="2.0" leading="3" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P9" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT"/>
|
|
||||||
<paraStyle name="P10" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT"/>
|
|
||||||
<paraStyle name="P11" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT"/>
|
|
||||||
<paraStyle name="P12" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P13" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P14" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P15" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P16" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P17" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P18" fontName="Helvetica" fontSize="2.0" leading="3"/>
|
|
||||||
<paraStyle name="Standard" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Heading" fontName="Helvetica" fontSize="14.0" leading="17" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Text body" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="List" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Caption" fontName="Helvetica" fontSize="12.0" leading="15" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Index" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="terp_header" fontName="Helvetica-Bold" fontSize="12.0" leading="15" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_8" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_space" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_header_Centre" fontName="Helvetica-Bold" fontSize="14.0" leading="17" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Centre_8" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Centre_9" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_9" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Bold_8" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Bold_9" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="Table Contents" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details_Right" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_General" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Table Heading" fontName="Helvetica" alignment="CENTER"/>
|
|
||||||
<paraStyle name="payslip_adj" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<images/>
|
|
||||||
</stylesheet>
|
|
||||||
<story>
|
|
||||||
<para style="P1">[[repeatIn(objects,'o')]]</para>
|
|
||||||
<blockTable colWidths="539.0" style="Table1">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P6">PaySlip Lines by Contribution Register</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="P7">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P5">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P16"/>
|
|
||||||
<blockTable colWidths="254.0,143.0,141.0" style="Table3">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P17">Register Name</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P13">Date From</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P14">Date To</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="254.0,143.0,141.0" style="Table4">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ o.name or '']]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ data['form']['date_from'] or '']]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ data['form']['date_to'] or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="terp_default_Bold_8"/>
|
|
||||||
<para style="terp_default_Bold_8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<blockTable colWidths="194.0,35.0,126.0,72.0,43.0,69.0" style="Table2">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P9">PaySlip Name</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P9">Code</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P9">Name</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P9">Quantity/Rate</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P9">Amount</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P10">Total </para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="P18">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<section>
|
|
||||||
<para style="P2">[[repeatIn(get_payslip_lines(o),'r') ]]</para>
|
|
||||||
<blockTable colWidths="194.0,35.0,126.0,72.0,43.0,68.0" style="Table16">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P12">[[ r.get('payslip_name', False) ]]<font face="Helvetica">[[ r.get('payslip_name', False) and ( setTag('para','para',{'style':'terp_default_8'})) or removeParentNode('font')]]</font></para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P2">[[ r['code'] ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P2">[[ r['name'] ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P2">[[ formatLang(r['quantity']) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P2">[[ formatLang(r['amount']) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P3">[[ formatLang(r['total'], currency_obj = o.company_id and o.company_id.currency_id)]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
</section>
|
|
||||||
<blockTable colWidths="397.0,31.0,111.0" style="Table5">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P15">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P15">Total:</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P11">[[ formatLang(sum_total(), currency_obj = o.company_id and o.company_id.currency_id)]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="Standard">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</story>
|
|
||||||
</document>
|
|
||||||
|
|
|
@ -21,8 +21,9 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
from openerp.osv import osv
|
||||||
from openerp.report import report_sxw
|
from openerp.report import report_sxw
|
||||||
from openerp.tools import amount_to_text_en
|
|
||||||
|
|
||||||
class payslip_report(report_sxw.rml_parse):
|
class payslip_report(report_sxw.rml_parse):
|
||||||
|
|
||||||
|
@ -37,12 +38,17 @@ class payslip_report(report_sxw.rml_parse):
|
||||||
res = []
|
res = []
|
||||||
ids = []
|
ids = []
|
||||||
for id in range(len(obj)):
|
for id in range(len(obj)):
|
||||||
if obj[id].appears_on_payslip == True:
|
if obj[id].appears_on_payslip is True:
|
||||||
ids.append(obj[id].id)
|
ids.append(obj[id].id)
|
||||||
if ids:
|
if ids:
|
||||||
res = payslip_line.browse(self.cr, self.uid, ids)
|
res = payslip_line.browse(self.cr, self.uid, ids)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
report_sxw.report_sxw('report.payslip', 'hr.payslip', 'hr_payroll/report/report_payslip.rml', parser=payslip_report)
|
|
||||||
|
class wrapped_report_payslip(osv.AbstractModel):
|
||||||
|
_name = 'report.hr_payroll.report_payslip'
|
||||||
|
_inherit = 'report.abstract_report'
|
||||||
|
_template = 'hr_payroll.report_payslip'
|
||||||
|
_wrapped_report_class = payslip_report
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -1,340 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<document filename="test.pdf">
|
|
||||||
<template title="Test" author="Martin Simon" allowSplitting="20">
|
|
||||||
<pageTemplate id="first">
|
|
||||||
<frame id="first" x1="28.0" y1="28.0" width="539" height="786"/>
|
|
||||||
</pageTemplate>
|
|
||||||
</template>
|
|
||||||
<stylesheet>
|
|
||||||
<blockTableStyle id="Standard_Outline">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table1">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table2">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="3,0" stop="3,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="3,-1" stop="3,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table3">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table4">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="3,0" stop="3,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="3,-1" stop="3,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table5">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="3,0" stop="3,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="3,-1" stop="3,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table6">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="3,0" stop="3,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="3,-1" stop="3,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table8">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="4,-1" stop="4,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table9">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="3,-1" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="4,-1" stop="4,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table13">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<initialize>
|
|
||||||
<paraStyle name="all" alignment="justify"/>
|
|
||||||
</initialize>
|
|
||||||
<paraStyle name="P1" fontName="Helvetica" fontSize="2.0" leading="3"/>
|
|
||||||
<paraStyle name="P2" rightIndent="-56.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P3" rightIndent="-56.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P4" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P5" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P6" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P7" fontName="Helvetica-Bold" fontSize="14.0" leading="17" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P9" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="2.0" leading="3" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P10" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="2.0" leading="3" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P11" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="2.0" leading="3" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P12" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P13" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT"/>
|
|
||||||
<paraStyle name="P14" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT"/>
|
|
||||||
<paraStyle name="P15" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P16" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="2.0" leading="3" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="Standard" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Heading" fontName="Helvetica" fontSize="14.0" leading="17" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Text body" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="List" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Caption" fontName="Helvetica" fontSize="12.0" leading="15" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Index" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="terp_header" fontName="Helvetica-Bold" fontSize="12.0" leading="15" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_8" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_space" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_header_Centre" fontName="Helvetica-Bold" fontSize="14.0" leading="17" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Centre_8" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Centre_9" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_9" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Bold_8" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Bold_9" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="Table Contents" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details_Right" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_General" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Table Heading" fontName="Helvetica" alignment="CENTER"/>
|
|
||||||
<paraStyle name="payslip_adj" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<images/>
|
|
||||||
</stylesheet>
|
|
||||||
<story>
|
|
||||||
<para style="P2">[[repeatIn(objects,'o')]]</para>
|
|
||||||
<blockTable colWidths="539.0" style="Table1">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P7">Pay Slip</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="terp_header_Centre">
|
|
||||||
<font face="Helvetica" size="6.0">[[o.credit_note==False and removeParentNode('para')]]</font>
|
|
||||||
<font face="Helvetica-Bold" size="14.0">Credit</font>
|
|
||||||
<font face="Helvetica" size="14.0"/>
|
|
||||||
<font face="Helvetica-Bold" size="14.0">Note</font>
|
|
||||||
</para>
|
|
||||||
<para style="P8">([[o.name or removeParentNode('para')]])</para>
|
|
||||||
<blockTable colWidths="63.0,206.0,89.0,181.0" style="Table2">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P15">Name</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P15">[[o.employee_id.name]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P15">Designation </para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ o.employee_id.job_id.name or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="63.0,476.0" style="Table3">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Bold_8">
|
|
||||||
<font face="Helvetica">Address </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[o.employee_id.address_home_id and o.employee_id.address_home_id.name or '' ]]
|
|
||||||
[[o.employee_id.address_home_id and display_address(o.employee_id.address_home_id)]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="63.0,206.0,89.0,181.0" style="Table4">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P15">Email</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ o.employee_id.work_email or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Bold_8">
|
|
||||||
<font face="Helvetica">Identification No</font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ o.employee_id.identification_id or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="63.0,206.0,89.0,181.0" style="Table5">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P15">Reference</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ o.number or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P15">Bank Account</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ o.employee_id.otherid or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="63.0,206.0,89.0,181.0" style="Table6">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P15">Date From</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ o.date_from or '']]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Bold_8">
|
|
||||||
<font face="Helvetica" size="8.0">Date To</font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ o.date_to or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="P6">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P6"/>
|
|
||||||
<blockTable colWidths="67.0,218.0,88.0,85.0,81.0" style="Table8">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P13">Code</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P13">Name</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P13">Quantity/Rate</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P13">Amount</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P14">Total</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<section>
|
|
||||||
<para style="P4">[[repeatIn(get_payslip_lines(o.line_ids),'p') ]]</para>
|
|
||||||
<blockTable colWidths="67.0,218.0,88.0,85.0,81.0" style="Table9">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ p.code ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ p.name ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ formatLang(p.quantity) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P4">[[ formatLang(p.amount) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P5">[[ formatLang(p.total, currency_obj = o.company_id and o.company_id.currency_id)]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
</section>
|
|
||||||
<para style="P10">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P16">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P6">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P1">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<blockTable colWidths="269.0,269.0" style="Table13">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P4">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P12">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P12">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P12">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P12">Authorized Signature </para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="P3">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</story>
|
|
||||||
</document>
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#-*- coding:utf-8 -*-
|
#-*- coding:utf-8 -*-
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# OpenERP, Open Source Management Solution
|
# OpenERP, Open Source Management Solution
|
||||||
|
@ -21,8 +20,9 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
from openerp.osv import osv
|
||||||
from openerp.report import report_sxw
|
from openerp.report import report_sxw
|
||||||
from openerp.tools import amount_to_text_en
|
|
||||||
|
|
||||||
class payslip_details_report(report_sxw.rml_parse):
|
class payslip_details_report(report_sxw.rml_parse):
|
||||||
|
|
||||||
|
@ -113,6 +113,11 @@ class payslip_details_report(report_sxw.rml_parse):
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
report_sxw.report_sxw('report.paylip.details', 'hr.payslip', 'hr_payroll/report/report_payslip_details.rml', parser=payslip_details_report)
|
|
||||||
|
class wrapped_report_payslipdetails(osv.AbstractModel):
|
||||||
|
_name = 'report.hr_payroll.report_payslipdetails'
|
||||||
|
_inherit = 'report.abstract_report'
|
||||||
|
_template = 'hr_payroll.report_payslipdetails'
|
||||||
|
_wrapped_report_class = payslip_details_report
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -1,426 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<document filename="test.pdf">
|
|
||||||
<template title="Test" author="Martin Simon" allowSplitting="20">
|
|
||||||
<pageTemplate id="first">
|
|
||||||
<frame id="first" x1="28.0" y1="28.0" width="539" height="786"/>
|
|
||||||
</pageTemplate>
|
|
||||||
</template>
|
|
||||||
<stylesheet>
|
|
||||||
<blockTableStyle id="Standard_Outline">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table1">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table2">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="3,0" stop="3,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="3,-1" stop="3,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table3">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table4">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="3,0" stop="3,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="3,-1" stop="3,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table5">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="3,0" stop="3,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="3,-1" stop="3,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table6">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#cccccc" start="3,0" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#cccccc" start="3,0" stop="3,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="3,-1" stop="3,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table10">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table11">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table12">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="3,-1" stop="3,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table8">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table7">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="4,-1" stop="4,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="5,-1" stop="5,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table16">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="3,-1" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="4,-1" stop="4,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="5,-1" stop="5,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table13">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<initialize>
|
|
||||||
<paraStyle name="all" alignment="justify"/>
|
|
||||||
</initialize>
|
|
||||||
<paraStyle name="P1" fontName="Helvetica" fontSize="2.0" leading="3"/>
|
|
||||||
<paraStyle name="P2" fontName="Helvetica" fontSize="2.0" leading="3"/>
|
|
||||||
<paraStyle name="P3" rightIndent="-56.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P4" rightIndent="-56.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P5" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P6" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P7" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P8" fontName="Helvetica-Bold" fontSize="14.0" leading="17" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P9" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P10" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P11" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="2.0" leading="3" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P12" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P13" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT"/>
|
|
||||||
<paraStyle name="P14" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT"/>
|
|
||||||
<paraStyle name="P15" fontName="Helvetica-Bold" fontSize="8.0" leading="10"/>
|
|
||||||
<paraStyle name="P16" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P17" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P18" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P19" fontName="Helvetica" fontSize="2.0" leading="3" alignment="LEFT"/>
|
|
||||||
<paraStyle name="P20" fontName="Helvetica" fontSize="2.0" leading="3"/>
|
|
||||||
<paraStyle name="P21" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P22" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT"/>
|
|
||||||
<paraStyle name="P23" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT"/>
|
|
||||||
<paraStyle name="P24" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P25" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="P26" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="Standard" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Heading" fontName="Helvetica" fontSize="14.0" leading="17" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Text body" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="List" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Caption" fontName="Helvetica" fontSize="12.0" leading="15" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Index" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="terp_header" fontName="Helvetica-Bold" fontSize="12.0" leading="15" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_8" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_space" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_header_Centre" fontName="Helvetica-Bold" fontSize="14.0" leading="17" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Centre_8" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Centre_9" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_9" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Bold_8" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Bold_9" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_10" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="Table Contents" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details_Right" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_General" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Table Heading" fontName="Helvetica" alignment="CENTER"/>
|
|
||||||
<paraStyle name="payslip_adj" rightIndent="0.0" leftIndent="0.0" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<images/>
|
|
||||||
</stylesheet>
|
|
||||||
<story>
|
|
||||||
<para style="P3">[[repeatIn(objects,'o')]]</para>
|
|
||||||
<blockTable colWidths="539.0" style="Table1">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P8">Pay Slip Details</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="terp_header_Centre">
|
|
||||||
<font face="Helvetica" size="6.0">[[o.credit_note==False and removeParentNode('para')]]</font>
|
|
||||||
<font face="Helvetica-Bold" size="14.0">Credit</font>
|
|
||||||
<font face="Helvetica" size="14.0"/>
|
|
||||||
<font face="Helvetica-Bold" size="14.0">Note</font>
|
|
||||||
</para>
|
|
||||||
<para style="P9">([[o.name or removeParentNode('para')]])</para>
|
|
||||||
<blockTable colWidths="63.0,206.0,89.0,181.0" style="Table2">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P16">Name</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P16">[[o.employee_id.name]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P16">Designation </para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P5">[[ o.employee_id.job_id.name or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="63.0,476.0" style="Table3">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Bold_8">
|
|
||||||
<font face="Helvetica">Address </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P5">[[o.employee_id.address_home_id and o.employee_id.address_home_id.name or '' ]]
|
|
||||||
[[o.employee_id.address_home_id and display_address(o.employee_id.address_home_id)]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="63.0,206.0,89.0,181.0" style="Table4">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P16">Email</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P5">[[ o.employee_id.work_email or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Bold_8">
|
|
||||||
<font face="Helvetica">Identification No</font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P5">[[ o.employee_id.identification_id or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="63.0,206.0,89.0,181.0" style="Table5">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P16">Reference</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P5">[[ o.number or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P16">Bank Account</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P5">[[ o.employee_id.otherid or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="63.0,206.0,89.0,181.0" style="Table6">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P16">Date From</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P5">[[ o.date_from or '']]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Bold_8">
|
|
||||||
<font face="Helvetica" size="8.0">Date To</font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P5">[[ o.date_to or '' ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="P7"/>
|
|
||||||
<para style="P11">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P5">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<blockTable colWidths="539.0" style="Table10">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P10">Details by Salary Rule Category: </para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="54.0,388.0,97.0" style="Table11">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P15">Code</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P15">Salary Rule Category</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P14">Total</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="P1">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<section>
|
|
||||||
<para style="P16">[[repeatIn(get_details_by_rule_category(o.details_by_salary_rule_category),'h') ]]</para>
|
|
||||||
<blockTable colWidths="54.0,388.0,97.0" style="Table12">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P16">
|
|
||||||
<font face="Helvetica">[[ h['code'] ]]</font>
|
|
||||||
<font face="Helvetica">[[ h['level']!=0 and ( setTag('para','para',{'style':'terp_default_8'})) or removeParentNode('font')]]</font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P17"><font face="Helvetica" color="white">[[ '..'*h['level'] ]]</font>[[ h['rule_category'] ]]<font face="Helvetica">[[ h['level']!=0 and ( setTag('para','para',{'style':'terp_default_8'})) or removeParentNode('font') ]]</font></para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P6">[[ formatLang(h['total'], currency_obj = o.company_id and o.company_id.currency_id)]] <font face="Helvetica" size="8.0">[[ h['level']==0 and ( setTag('para','para',{'style':'terp_default_10'})) or removeParentNode('font') ]]</font></para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
</section>
|
|
||||||
<para style="P7">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<blockTable colWidths="539.0" style="Table8">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P10">Payslip Lines by Contribution Register:</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="114.0,42.0,170.0,85.0,56.0,71.0" style="Table7">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P13">Register Name</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P13">Code</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P13">Name</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P13">Quantity/Rate</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P13">Amount</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P14">Total</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<section>
|
|
||||||
<para style="P16">[[repeatIn(get_lines_by_contribution_register(o.details_by_salary_rule_category),'r') ]]</para>
|
|
||||||
<blockTable colWidths="113.0,44.0,169.0,85.0,56.0,72.0" style="Table16">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P16">[[ r.get('register_name', False) ]]<font face="Helvetica">[[ h.get('register_name', False) and ( setTag('para','para',{'style':'terp_default_8'})) or removeParentNode('font')]]</font></para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P5">[[ r['code'] ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P5">[[ r['name'] ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P5">[[ formatLang(r['quantity']) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P5">[[ formatLang(r['amount']) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P6">[[ formatLang(r['total'], currency_obj = o.company_id and o.company_id.currency_id)]]<font face="Helvetica">[[ r.get('register_name', False) and ( setTag('para','para',{'style':'terp_default_10'})) or removeParentNode('font')]]</font></para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
</section>
|
|
||||||
<blockTable colWidths="269.0,269.0" style="Table13">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="P5">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="P12">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P12">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P12">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="P12">Authorized Signature </para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="P4">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</story>
|
|
||||||
</document>
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
-
|
|
||||||
In order to test the PDF reports defined on HR Payroll, we will print Employees' Salary Structure
|
|
||||||
-
|
|
||||||
Print HR Payslip
|
|
||||||
-
|
|
||||||
!python {model: hr.payslip}: |
|
|
||||||
import os
|
|
||||||
import openerp.report
|
|
||||||
from openerp import tools
|
|
||||||
data, format = openerp.report.render_report(cr, uid, [ref('hr_payroll.hr_payslip_salaryslipofbonamyforjune0')], 'payslip.pdf', {}, {})
|
|
||||||
if tools.config['test_report_directory']:
|
|
||||||
file(os.path.join(tools.config['test_report_directory'], 'hr_payroll-payslip_report.'+format), 'wb+').write(data)
|
|
|
@ -107,8 +107,29 @@
|
||||||
date_from: '2011-09-30'
|
date_from: '2011-09-30'
|
||||||
date_to: '2011-09-01'
|
date_to: '2011-09-01'
|
||||||
-
|
-
|
||||||
I print the report.
|
I print the payslip report
|
||||||
-
|
-
|
||||||
!python {model: payslip.lines.contribution.register}: |
|
!python {model: hr.payslip}: |
|
||||||
self.print_report(cr, uid, [ref('payslip_lines_contribution_register0')], context={'active_ids': [ref('hr_houserent_register')]})
|
import os
|
||||||
|
import openerp.report
|
||||||
|
from openerp import tools
|
||||||
|
data, format = openerp.report.render_report(cr, uid, [ref('hr_payslip_0')], 'hr_payroll.report_payslip', {}, {})
|
||||||
|
if tools.config['test_report_directory']:
|
||||||
|
file(os.path.join(tools.config['test_report_directory'], 'hr_payroll-payslip.'+format), 'wb+').write(data)
|
||||||
|
-
|
||||||
|
I print the payslip details report
|
||||||
|
-
|
||||||
|
!python {model: hr.payslip}: |
|
||||||
|
import os
|
||||||
|
import openerp.report
|
||||||
|
from openerp import tools
|
||||||
|
data, format = openerp.report.render_report(cr, uid, [ref('hr_payslip_0')], 'hr_payroll.report_payslipdetails', {}, {})
|
||||||
|
if tools.config['test_report_directory']:
|
||||||
|
file(os.path.join(tools.config['test_report_directory'], 'hr_payroll-payslipdetails.'+format), 'wb+').write(data)
|
||||||
|
-
|
||||||
|
I print the contribution register report
|
||||||
|
-
|
||||||
|
!python {model: hr.contribution.register}: |
|
||||||
|
ctx={'model': 'hr.contribution.register', 'active_ids': [ref('hr_houserent_register')]}
|
||||||
|
from openerp.tools import test_reports
|
||||||
|
test_reports.try_report_action(cr, uid, 'action_payslip_lines_contribution_register', context=ctx, our_module='hr_payroll')
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
<template id="report_contributionregister">
|
||||||
|
<t t-call="report.html_container">
|
||||||
|
<t t-foreach="docs" t-as="o">
|
||||||
|
<t t-call="report.external_layout">
|
||||||
|
<div class="page">
|
||||||
|
<h2>PaySlip Lines by Contribution Register</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row mt32 mb32">
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<strong>Register Name:</strong>
|
||||||
|
<p t-field="o.name"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<strong>Date From:</strong>
|
||||||
|
<p t-esc="data['form']['date_from']"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<strong>Date To:</strong>
|
||||||
|
<p t-esc="data['form']['date_to']"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>PaySlip Name</th>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Quantity/Rate</th>
|
||||||
|
<th>Amount</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="get_payslip_lines(o)" t-as="r">
|
||||||
|
<td><span t-esc="r.get('payslip_name')"/></td>
|
||||||
|
<td><span t-esc="r['code']"/></td>
|
||||||
|
<td><span t-esc="r['name']"/></td>
|
||||||
|
<td><span t-esc="formatLang(r['quantity'])"/></td>
|
||||||
|
<td><span t-esc="formatLang(r['amount'])"/></td>
|
||||||
|
<td><span t-esc=" formatLang(r['total'], currency_obj=o.company_id and o.company_id.currency_id)"/></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-4 pull-right">
|
||||||
|
<table class="table table-condensed">
|
||||||
|
<tr class="border-black">
|
||||||
|
<td><strong>Total</strong></td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span t-esc="formatLang(sum_total(), currency_obj = o.company_id and o.company_id.currency_id)"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
<template id="report_payslip">
|
||||||
|
<t t-call="report.html_container">
|
||||||
|
<t t-foreach="docs" t-as="o">
|
||||||
|
<t t-call="report.external_layout">
|
||||||
|
<div class="page">
|
||||||
|
<h2>Pay Slip</h2>
|
||||||
|
<p t-field="o.name"/>
|
||||||
|
|
||||||
|
<table class="table table-condensed table-bordered">
|
||||||
|
<tr>
|
||||||
|
<td><strong>Name</strong></td>
|
||||||
|
<td><span t-field="o.employee_id"/></td>
|
||||||
|
<td><strong>Designation</strong></td>
|
||||||
|
<td><span t-field="o.employee_id.job_id"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Address</strong></td>
|
||||||
|
<td colspan="3">
|
||||||
|
<div t-filed="o.employee_id.address_home_id"
|
||||||
|
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Email</strong></td>
|
||||||
|
<td><span t-field="o.employee_id.work_email"/></td>
|
||||||
|
<td><strong>Identification No</strong></td>
|
||||||
|
<td><span t-field="o.employee_id.job_id"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Reference</strong></td>
|
||||||
|
<td><span t-field="o.number"/></td>
|
||||||
|
<td><strong>Bank Account</strong></td>
|
||||||
|
<td><span t-field="o.employee_id.otherid"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Date From</strong></td>
|
||||||
|
<td><span t-field="o.date_from"/></td>
|
||||||
|
<td><strong>Date To</strong></td>
|
||||||
|
<td><span t-field="o.date_to"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="table table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Quantity/rate</th>
|
||||||
|
<th>Amount</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="get_payslip_lines(o.line_ids)" t-as="p">
|
||||||
|
<td><span t-field="p.code"/></td>
|
||||||
|
<td><span t-field="p.name"/></td>
|
||||||
|
<td><span t-field="p.quantity"/></td>
|
||||||
|
<td><span t-esc="formatLang(p.amount, currency_obj=o.company_id.currency_id)"/></td>
|
||||||
|
<td><span t-esc="formatLang(p.total, currency_obj=o.company_id.currency_id)"/></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p class="text-right"><strong>Authorized signature</strong></p>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
<template id="report_payslipdetails">
|
||||||
|
<t t-call="report.html_container">
|
||||||
|
<t t-foreach="docs" t-as="o">
|
||||||
|
<t t-call="report.external_layout">
|
||||||
|
<div class="page">
|
||||||
|
<h2>Pay Slip</h2>
|
||||||
|
<p t-field="o.name"/>
|
||||||
|
|
||||||
|
<table class="table table-condensed table-bordered">
|
||||||
|
<tr>
|
||||||
|
<td><strong>Name</strong></td>
|
||||||
|
<td><span t-field="o.employee_id"/></td>
|
||||||
|
<td><strong>Designation</strong></td>
|
||||||
|
<td><span t-field="o.employee_id.job_id"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Address</strong></td>
|
||||||
|
<td colspan="3">
|
||||||
|
<div t-field="o.employee_id.address_home_id"
|
||||||
|
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Email</strong></td>
|
||||||
|
<td><span t-field="o.employee_id.work_email"/></td>
|
||||||
|
<td><strong>Identification No</strong></td>
|
||||||
|
<td><span t-field="o.employee_id.job_id"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Reference</strong></td>
|
||||||
|
<td><span t-field="o.number"/></td>
|
||||||
|
<td><strong>Bank Account</strong></td>
|
||||||
|
<td><span t-field="o.employee_id.otherid"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Date From</strong></td>
|
||||||
|
<td><span t-field="o.date_from"/></td>
|
||||||
|
<td><strong>Date To</strong></td>
|
||||||
|
<td><span t-field="o.date_to"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Details by Salary Rule Category</h3>
|
||||||
|
<table class="table table-condensed mb32">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Salary Rule Category</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="get_details_by_rule_category(o.details_by_salary_rule_category)" t-as="h">
|
||||||
|
<td>
|
||||||
|
<span t-esc="h['code']"/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span t-esc="'..'*h['level']"/><span t-esc="h['rule_category']"/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span t-esc="formatLang(h['total'], currency_obj=o.company_id.currency_id)"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Payslip Lines by Contribution Register</h3>
|
||||||
|
<table class="table table-condensed mt32">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Quantity/rate</th>
|
||||||
|
<th>Amount</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="get_lines_by_contribution_register(o.line_ids)" t-as="p">
|
||||||
|
<td><span t-esc="p.get('code', '')"/></td>
|
||||||
|
<td><span t-esc="p.get('name', '')"/></td>
|
||||||
|
<td><span t-esc="p.get('quantity', '')"/></td>
|
||||||
|
<td><span t-esc="formatLang(p.get('amount', 0))"/></td>
|
||||||
|
<td><span t-esc="formatLang(p.get('total', 0), currency_obj=o.company_id.currency_id)"/></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p class="text-right"><strong>Authorized signature</strong></p>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -22,9 +22,9 @@
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dateutil import relativedelta
|
from dateutil import relativedelta
|
||||||
|
|
||||||
from openerp.osv import fields, osv
|
from openerp.osv import fields, osv
|
||||||
|
|
||||||
|
|
||||||
class payslip_lines_contribution_register(osv.osv_memory):
|
class payslip_lines_contribution_register(osv.osv_memory):
|
||||||
_name = 'payslip.lines.contribution.register'
|
_name = 'payslip.lines.contribution.register'
|
||||||
_description = 'PaySlip Lines by Contribution Registers'
|
_description = 'PaySlip Lines by Contribution Registers'
|
||||||
|
@ -44,11 +44,8 @@ class payslip_lines_contribution_register(osv.osv_memory):
|
||||||
'model': 'hr.contribution.register',
|
'model': 'hr.contribution.register',
|
||||||
'form': self.read(cr, uid, ids, [], context=context)[0]
|
'form': self.read(cr, uid, ids, [], context=context)[0]
|
||||||
}
|
}
|
||||||
return {
|
return self.pool['report'].get_action(
|
||||||
'type': 'ir.actions.report.xml',
|
cr, uid, [], 'hr_payroll.report_contributionregister', data=datas, context=context
|
||||||
'report_name': 'contribution.register.lines',
|
)
|
||||||
'datas': datas,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -80,6 +80,7 @@ class hr_applicant(osv.Model):
|
||||||
_description = "Applicant"
|
_description = "Applicant"
|
||||||
_order = "id desc"
|
_order = "id desc"
|
||||||
_inherit = ['mail.thread', 'ir.needaction_mixin']
|
_inherit = ['mail.thread', 'ir.needaction_mixin']
|
||||||
|
|
||||||
_track = {
|
_track = {
|
||||||
'stage_id': {
|
'stage_id': {
|
||||||
# this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages
|
# this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages
|
||||||
|
@ -87,6 +88,7 @@ class hr_applicant(osv.Model):
|
||||||
'hr_recruitment.mt_applicant_stage_changed': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence > 1,
|
'hr_recruitment.mt_applicant_stage_changed': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence > 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
_mail_mass_mailing = _('Applicants')
|
||||||
|
|
||||||
def _get_default_department_id(self, cr, uid, context=None):
|
def _get_default_department_id(self, cr, uid, context=None):
|
||||||
""" Gives default department by checking if present in the context """
|
""" Gives default department by checking if present in the context """
|
||||||
|
|
|
@ -35,7 +35,7 @@ reports.""",
|
||||||
'author': 'OpenERP SA',
|
'author': 'OpenERP SA',
|
||||||
'website': 'http://www.openerp.com',
|
'website': 'http://www.openerp.com',
|
||||||
'images': ['images/hr_bill_task_work.jpeg','images/hr_type_of_invoicing.jpeg'],
|
'images': ['images/hr_bill_task_work.jpeg','images/hr_type_of_invoicing.jpeg'],
|
||||||
'depends': ['account', 'hr_timesheet'],
|
'depends': ['account', 'hr_timesheet', 'report'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'hr_timesheet_invoice_data.xml',
|
'hr_timesheet_invoice_data.xml',
|
||||||
|
@ -47,6 +47,7 @@ reports.""",
|
||||||
'wizard/hr_timesheet_analytic_profit_view.xml',
|
'wizard/hr_timesheet_analytic_profit_view.xml',
|
||||||
'wizard/hr_timesheet_invoice_create_view.xml',
|
'wizard/hr_timesheet_invoice_create_view.xml',
|
||||||
'wizard/hr_timesheet_invoice_create_final_view.xml',
|
'wizard/hr_timesheet_invoice_create_final_view.xml',
|
||||||
|
'views/report_analyticprofit.xml',
|
||||||
],
|
],
|
||||||
'demo': ['hr_timesheet_invoice_demo.xml'],
|
'demo': ['hr_timesheet_invoice_demo.xml'],
|
||||||
'test': ['test/test_hr_timesheet_invoice.yml',
|
'test': ['test/test_hr_timesheet_invoice.yml',
|
||||||
|
|
|
@ -2,13 +2,12 @@
|
||||||
<openerp>
|
<openerp>
|
||||||
<data>
|
<data>
|
||||||
<report
|
<report
|
||||||
auto="False"
|
id="action_report_analytic_profit"
|
||||||
id="report_analytical_profit"
|
|
||||||
menu="False"
|
|
||||||
model="account.analytic.line"
|
model="account.analytic.line"
|
||||||
name="account.analytic.profit"
|
name="hr_timesheet_invoice.report_analyticprofit"
|
||||||
rml="hr_timesheet_invoice/report/account_analytic_profit.rml"
|
file="hr_timesheet_invoice.report_analyticprofit"
|
||||||
string="Timesheet Profit"/>
|
report_type="qweb-pdf"
|
||||||
|
string="Timesheet Profit"
|
||||||
|
/>
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
from openerp.report import report_sxw
|
from openerp.report import report_sxw
|
||||||
|
from openerp.osv import osv
|
||||||
|
|
||||||
|
|
||||||
class account_analytic_profit(report_sxw.rml_parse):
|
class account_analytic_profit(report_sxw.rml_parse):
|
||||||
def __init__(self, cr, uid, name, context):
|
def __init__(self, cr, uid, name, context):
|
||||||
|
@ -30,6 +32,7 @@ class account_analytic_profit(report_sxw.rml_parse):
|
||||||
'journal_ids': self._journal_ids,
|
'journal_ids': self._journal_ids,
|
||||||
'line': self._line,
|
'line': self._line,
|
||||||
})
|
})
|
||||||
|
|
||||||
def _user_ids(self, lines):
|
def _user_ids(self, lines):
|
||||||
user_obj = self.pool['res.users']
|
user_obj = self.pool['res.users']
|
||||||
ids=list(set([b.user_id.id for b in lines]))
|
ids=list(set([b.user_id.id for b in lines]))
|
||||||
|
@ -116,6 +119,11 @@ class account_analytic_profit(report_sxw.rml_parse):
|
||||||
])
|
])
|
||||||
return line_obj.browse(self.cr, self.uid, ids)
|
return line_obj.browse(self.cr, self.uid, ids)
|
||||||
|
|
||||||
report_sxw.report_sxw('report.account.analytic.profit', 'account.analytic.line', 'addons/hr_timesheet_invoice/report/account_analytic_profit.rml', parser=account_analytic_profit)
|
|
||||||
|
class report_account_analytic_profit(osv.AbstractModel):
|
||||||
|
_name = 'report.hr_timesheet_invoice.report_analyticprofit'
|
||||||
|
_inherit = 'report.abstract_report'
|
||||||
|
_template = 'hr_timesheet_invoice.report_analyticprofit'
|
||||||
|
_wrapped_report_class = account_analytic_profit
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -1,341 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<document filename="test.pdf">
|
|
||||||
<template title="Invoice rate by user" author="OpenERP S.A. (sales@openerp.com)" allowSplitting="20">
|
|
||||||
<pageTemplate id="first">
|
|
||||||
<frame id="first" x1="35.0" y1="57.0" width="525" height="728"/>
|
|
||||||
</pageTemplate>
|
|
||||||
</template>
|
|
||||||
<stylesheet>
|
|
||||||
<blockTableStyle id="Standard_Outline">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table_Title">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table_header_Date">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#e6e6e6" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table_Content_Date">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="0,0" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="0,0" stop="0,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="1,0" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="1,0" stop="1,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEAFTER" colorName="#e6e6e6" start="2,0" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="2,0" stop="2,0"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table_Header_Employee">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="4,-1" stop="4,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="5,-1" stop="5,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="6,-1" stop="6,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table_Final_Total">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="4,-1" stop="4,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="5,-1" stop="5,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#000000" start="6,-1" stop="6,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table_Journal_Total_detail">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="0,-1" stop="0,-1"/>
|
|
||||||
<lineStyle kind="LINEBELOW" colorName="#cccccc" start="1,-1" stop="1,-1"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table_Journal_title">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<blockTableStyle id="Table1">
|
|
||||||
<blockAlignment value="LEFT"/>
|
|
||||||
<blockValign value="TOP"/>
|
|
||||||
</blockTableStyle>
|
|
||||||
<initialize>
|
|
||||||
<paraStyle name="all" alignment="justify"/>
|
|
||||||
</initialize>
|
|
||||||
<paraStyle name="Standard" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Text body" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Heading" fontName="Helvetica" fontSize="14.0" leading="17" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="List" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Table Contents" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Table Heading" fontName="Helvetica" alignment="CENTER"/>
|
|
||||||
<paraStyle name="Caption" fontName="Helvetica" fontSize="12.0" leading="15" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Index" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Preformatted Text" fontName="Courier" fontSize="10.0" leading="13" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="Footer" fontName="Helvetica"/>
|
|
||||||
<paraStyle name="Horizontal Line" fontName="Helvetica" fontSize="6.0" leading="8" spaceBefore="0.0" spaceAfter="14.0"/>
|
|
||||||
<paraStyle name="terp_header" fontName="Helvetica-Bold" fontSize="12.0" leading="15" alignment="LEFT" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="Heading 9" fontName="Helvetica-Bold" fontSize="75%" leading="NaN" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_General" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="LEFT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_default_8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Bold_8" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_General_Centre" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_General_Right" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details_Centre" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="CENTER" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_tblheader_Details_Right" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="RIGHT" spaceBefore="6.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_default_Right_8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Centre_8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_header_Right" fontName="Helvetica-Bold" fontSize="15.0" leading="19" alignment="LEFT" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_header_Centre" fontName="Helvetica-Bold" fontSize="12.0" leading="15" alignment="CENTER" spaceBefore="12.0" spaceAfter="6.0"/>
|
|
||||||
<paraStyle name="terp_default_address" fontName="Helvetica" fontSize="10.0" leading="13" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_9" fontName="Helvetica" fontSize="9.0" leading="11" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Bold_9" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Centre_9" fontName="Helvetica" fontSize="9.0" leading="11" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Right_9_bold" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_centre_bold_9" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_2" fontName="Helvetica" fontSize="2.0" leading="3" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Right_9" fontName="Helvetica" fontSize="9.0" leading="11" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<paraStyle name="terp_default_Right_9_bold_U" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
|
||||||
<images/>
|
|
||||||
</stylesheet>
|
|
||||||
<story>
|
|
||||||
<blockTable colWidths="175.0,175.0,175.0" repeatRows="1" style="Table_Title">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_header_Centre">Invoice rate by user</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="terp_default_8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="terp_default_8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<blockTable colWidths="175.0,175.0,175.0" style="Table_header_Date">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_General_Centre">Period from startdate</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_General_Centre">Period to enddate</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_General_Centre">Currency</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="175.0,175.0,175.0" style="Table_Content_Date">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Centre_8">[[ formatLang(data['form']['date_from'],date=True) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Centre_8">[[ formatLang (data['form']['date_to'] ,date=True)]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Centre_8">[[ company.currency_id.name ]]</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="terp_default_8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<para style="terp_default_8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<blockTable colWidths="159.0,62.0,63.0,68.0,65.0,53.0,52.0" style="Table_Header_Employee">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details">User or Journal Name</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Right">Units</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Right">Theorical</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Right">Income</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Right">Cost</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Right">Profit</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_tblheader_Details_Right">Eff.</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="terp_default_2">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<blockTable colWidths="154.0,65.0,60.0,72.0,66.0,52.0,51.0" style="Table_Final_Total">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Bold_9">Totals:</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9_bold_U">[[ reduce(lambda x, y: x+y['unit_amount'], line(data['form'], data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9_bold">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9_bold_U">[[ reduce(lambda x, y: x+y['amount'], line(data['form'],data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9_bold_U">[[ reduce(lambda x, y: x+y['cost'], line(data['form'],data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9_bold_U">[[ reduce(lambda x, y: x+y['profit'], line(data['form'],data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9_bold_U">[[ reduce(lambda x, y: x+y['cost'], line(data['form'],data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0) and round(reduce(lambda x, y: x+y['amount'], line(data['form'],data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0)/reduce(lambda x, y: x+y['cost'], line(data['form'],data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0)* -100, 2)]] %</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="terp_default_2"/>
|
|
||||||
<section>
|
|
||||||
<para style="terp_default_8">[[ repeatIn(user_ids(lines(data['form'])), 'e') ]]</para>
|
|
||||||
<blockTable colWidths="137.0,29.0,52.0,61.0,71.0,66.0,52.0,51.0" style="Table_Journal_Total_detail">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Bold_9">[[ e.name ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_8">[[ repeatIn(journal_ids(data['form'], [e.id]), 'j') ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9_bold_U">[[ reduce(lambda x, y: x+y['unit_amount'], line(data['form'], [j.id], [e.id]), 0) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9_bold_U">[[ reduce(lambda x, y: x+y['amount_th'], line(data['form'], [j.id], [e.id]), 0) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9_bold_U">[[ reduce(lambda x, y: x+y['amount'], line(data['form'], [j.id], [e.id]), 0) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9_bold_U">[[ reduce(lambda x, y: x+y['cost'], line(data['form'], [j.id], [e.id]), 0) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9_bold_U">[[ reduce(lambda x, y: x+y['profit'], line(data['form'], [j.id], [e.id]), 0) ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9_bold_U">[[reduce(lambda x, y: x+y['cost'], line(data['form'], [j.id], [e.id]), 0) and '%d' % (reduce(lambda x, y: x+y['amount'], line(data['form'], [j.id], [e.id]), 0) / reduce(lambda x, y: x+y['cost'], line(data['form'], [j.id], [e.id]), 0) * 100.0, 2)]] %</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<blockTable colWidths="154.0,65.0,61.0,71.0,66.0,52.0,51.0" style="Table_Journal_title">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_9">[[ j.name ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_9">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="terp_default_2">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
<section>
|
|
||||||
<para style="terp_default_8">[[ repeatIn(line(data['form'], [j.id],[e.id]), 'l') ]] </para>
|
|
||||||
<blockTable colWidths="155.0,64.0,61.0,71.0,66.0,52.0,50.0" style="Table1">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_8">[[ l['name'] ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9">[[ l['unit_amount'] ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9">[[ l['amount_th'] ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9">[[ l['amount'] ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9">[[ l['cost'] ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9">[[ l['profit'] ]]</para>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<para style="terp_default_Right_9">[[ l['eff'] ]] %</para>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</blockTable>
|
|
||||||
<para style="terp_default_2">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</section>
|
|
||||||
<para style="terp_default_2">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</section>
|
|
||||||
<para style="terp_default_8">
|
|
||||||
<font color="white"> </font>
|
|
||||||
</para>
|
|
||||||
</story>
|
|
||||||
</document>
|
|
|
@ -6,6 +6,6 @@
|
||||||
import openerp.report
|
import openerp.report
|
||||||
from openerp import tools
|
from openerp import tools
|
||||||
data_dict = {'model': 'ir.ui.menu', 'form': {'date_from': time.strftime('%Y-%m-01'), 'employee_ids': [[6,0,[ref('hr.employee_fp'), ref('hr.employee_qdp'),ref('hr.employee_al')]]], 'journal_ids': [[6,0,[ref('hr_timesheet.analytic_journal')]]], 'date_to': time.strftime('%Y-%m-%d')}}
|
data_dict = {'model': 'ir.ui.menu', 'form': {'date_from': time.strftime('%Y-%m-01'), 'employee_ids': [[6,0,[ref('hr.employee_fp'), ref('hr.employee_qdp'),ref('hr.employee_al')]]], 'journal_ids': [[6,0,[ref('hr_timesheet.analytic_journal')]]], 'date_to': time.strftime('%Y-%m-%d')}}
|
||||||
data, format = openerp.report.render_report(cr, uid, [], 'account.analytic.profit', data_dict, {})
|
data, format = openerp.report.render_report(cr, uid, [], 'hr_timesheet_invoice.report_analyticprofit', data_dict, {})
|
||||||
if tools.config['test_report_directory']:
|
if tools.config['test_report_directory']:
|
||||||
file(os.path.join(tools.config['test_report_directory'], 'hr_timesheet_invoice-account_analytic_profit_report.'+format), 'wb+').write(data)
|
file(os.path.join(tools.config['test_report_directory'], 'hr_timesheet_invoice-account_analytic_profit_report.'+format), 'wb+').write(data)
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
<template id="report_analyticprofit">
|
||||||
|
<t t-call="report.html_container">
|
||||||
|
<t t-call="report.external_layout">
|
||||||
|
<div class="page">
|
||||||
|
<h2>Invoice rate by user</h2>
|
||||||
|
|
||||||
|
<div class="row mt32 mb32">
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<strong>Period from startdate:</strong>
|
||||||
|
<p t-esc="formatLang(data['form']['date_from'],date=True)"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<strong>Period to enddate:</strong>
|
||||||
|
<p t-esc="formatLang(data['form']['date_to'],date=True)"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-3">
|
||||||
|
<strong>Currency:</strong>
|
||||||
|
<p t-esc="res_company.currency_id.name"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>User or Journal Name</th>
|
||||||
|
<th>Units</th>
|
||||||
|
<th>Theorical</th>
|
||||||
|
<th>Income</th>
|
||||||
|
<th>Cost</th>
|
||||||
|
<th>Profit</th>
|
||||||
|
<th>Eff.</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbdody>
|
||||||
|
<tr>
|
||||||
|
<td>Totals:</td>
|
||||||
|
<td><span t-esc="reduce(lambda x, y: x+y['unit_amount'], line(data['form'], data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0)"/></td>
|
||||||
|
<td></td>
|
||||||
|
<td><span t-esc="reduce(lambda x, y: x+y['amount'], line(data['form'],data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0)"/></td>
|
||||||
|
<td><span t-esc="reduce(lambda x, y: x+y['cost'], line(data['form'],data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0)"/></td>
|
||||||
|
<td><span t-esc="reduce(lambda x, y: x+y['profit'], line(data['form'],data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0)"/></td>
|
||||||
|
<td><span t-esc="reduce(lambda x, y: x+y['cost'], line(data['form'],data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0) and round(reduce(lambda x, y: x+y['amount'], line(data['form'],data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0)/reduce(lambda x, y: x+y['cost'], line(data['form'],data['form']['journal_ids'][0][2], data['form']['employee_ids'][0][2]), 0)* -100, 2)"/><span> %</span></td>
|
||||||
|
</tr>
|
||||||
|
<t t-foreach="user_ids(lines(data['form']))" t-as="e">
|
||||||
|
<t t-foreach="journal_ids(data['form'], [e.id])" t-as="j">
|
||||||
|
<tr>
|
||||||
|
<td><span t-esc="e.name"/></td>
|
||||||
|
<td><span t-esc="reduce(lambda x, y: x+y['unit_amount'], line(data['form'], [j.id], [e.id]), 0) "/></td>
|
||||||
|
<td><span t-esc="reduce(lambda x, y: x+y['amount_th'], line(data['form'], [j.id], [e.id]), 0)"/></td>
|
||||||
|
<td><span t-esc="reduce(lambda x, y: x+y['amount'], line(data['form'], [j.id], [e.id]), 0)"/></td>
|
||||||
|
<td><span t-esc="reduce(lambda x, y: x+y['cost'], line(data['form'], [j.id], [e.id]), 0) "/></td>
|
||||||
|
<td><span t-esc="reduce(lambda x, y: x+y['profit'], line(data['form'], [j.id], [e.id]), 0)"/></td>
|
||||||
|
<td>
|
||||||
|
<t t-if="reduce(lambda x, y: x+y['cost'], line(data['form'], [j.id], [e.id]), 0)">
|
||||||
|
<span t-esc="reduce(lambda x, y: x+y['amount'], line(data['form'], [j.id], [e.id]), 0) / reduce(lambda x, y: x+y['cost'], line(data['form'], [j.id], [e.id]), 0) * -100.0"/>
|
||||||
|
<span> %</span>
|
||||||
|
</t>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th colspan="7"><span t-esc="j.name"/></th>
|
||||||
|
</tr>
|
||||||
|
<tr t-foreach="line(data['form'], [j.id],[e.id])" t-as="l">
|
||||||
|
<td><span t-esc="l['name']"/></td>
|
||||||
|
<td><span t-esc="l['unit_amount']"/></td>
|
||||||
|
<td><span t-esc="l['amount_th']"/></td>
|
||||||
|
<td><span t-esc="l['amount']"/></td>
|
||||||
|
<td><span t-esc="l['cost']"/></td>
|
||||||
|
<td><span t-esc="l['profit']"/></td>
|
||||||
|
<td><span t-esc="l['eff']"/><span> %</span></td>
|
||||||
|
</tr>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</tbdody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -23,6 +23,7 @@ import datetime
|
||||||
from openerp.osv import fields, osv
|
from openerp.osv import fields, osv
|
||||||
from openerp.tools.translate import _
|
from openerp.tools.translate import _
|
||||||
|
|
||||||
|
|
||||||
class account_analytic_profit(osv.osv_memory):
|
class account_analytic_profit(osv.osv_memory):
|
||||||
_name = 'hr.timesheet.analytic.profit'
|
_name = 'hr.timesheet.analytic.profit'
|
||||||
_description = 'Print Timesheet Profit'
|
_description = 'Print Timesheet Profit'
|
||||||
|
@ -60,15 +61,12 @@ class account_analytic_profit(osv.osv_memory):
|
||||||
data['form']['journal_ids'] = [(6, 0, data['form']['journal_ids'])] # Improve me => Change the rml/sxw so that it can support withou [0][2]
|
data['form']['journal_ids'] = [(6, 0, data['form']['journal_ids'])] # Improve me => Change the rml/sxw so that it can support withou [0][2]
|
||||||
data['form']['employee_ids'] = [(6, 0, data['form']['employee_ids'])]
|
data['form']['employee_ids'] = [(6, 0, data['form']['employee_ids'])]
|
||||||
datas = {
|
datas = {
|
||||||
'ids': [],
|
'ids': [],
|
||||||
'model': 'account.analytic.line',
|
'model': 'account.analytic.line',
|
||||||
'form': data['form']
|
'form': data['form']
|
||||||
}
|
}
|
||||||
return {
|
return self.pool['report'].get_action(
|
||||||
'type': 'ir.actions.report.xml',
|
cr, uid, [], 'hr_timesheet_invoice.report_analyticprofit', data=datas, context=context
|
||||||
'report_name': 'account.analytic.profit',
|
)
|
||||||
'datas': datas,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -126,7 +126,7 @@ class mail_mail(osv.Model):
|
||||||
_logger.exception("Failed processing mail queue")
|
_logger.exception("Failed processing mail queue")
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _postprocess_sent_message(self, cr, uid, mail, context=None):
|
def _postprocess_sent_message(self, cr, uid, mail, context=None, mail_sent=True):
|
||||||
"""Perform any post-processing necessary after sending ``mail``
|
"""Perform any post-processing necessary after sending ``mail``
|
||||||
successfully, including deleting it completely along with its
|
successfully, including deleting it completely along with its
|
||||||
attachment if the ``auto_delete`` flag of the mail was set.
|
attachment if the ``auto_delete`` flag of the mail was set.
|
||||||
|
@ -145,9 +145,8 @@ class mail_mail(osv.Model):
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
|
|
||||||
def _get_partner_access_link(self, cr, uid, mail, partner=None, context=None):
|
def _get_partner_access_link(self, cr, uid, mail, partner=None, context=None):
|
||||||
""" Generate URLs for links in mails:
|
"""Generate URLs for links in mails: partner has access (is user):
|
||||||
- partner is an user and has read access to the document: direct link to document with model, res_id
|
link to action_mail_redirect action that will redirect to doc or Inbox """
|
||||||
"""
|
|
||||||
if partner and partner.user_ids:
|
if partner and partner.user_ids:
|
||||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||||
# the parameters to encode for the query and fragment part of url
|
# the parameters to encode for the query and fragment part of url
|
||||||
|
@ -167,11 +166,10 @@ class mail_mail(osv.Model):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def send_get_mail_subject(self, cr, uid, mail, force=False, partner=None, context=None):
|
def send_get_mail_subject(self, cr, uid, mail, force=False, partner=None, context=None):
|
||||||
""" If subject is void and record_name defined: '<Author> posted on <Resource>'
|
"""If subject is void, set the subject as 'Re: <Resource>' or
|
||||||
|
'Re: <mail.parent_id.subject>'
|
||||||
|
|
||||||
:param boolean force: force the subject replacement
|
:param boolean force: force the subject replacement
|
||||||
:param browse_record mail: mail.mail browse_record
|
|
||||||
:param browse_record partner: specific recipient partner
|
|
||||||
"""
|
"""
|
||||||
if (force or not mail.subject) and mail.record_name:
|
if (force or not mail.subject) and mail.record_name:
|
||||||
return 'Re: %s' % (mail.record_name)
|
return 'Re: %s' % (mail.record_name)
|
||||||
|
@ -180,12 +178,8 @@ class mail_mail(osv.Model):
|
||||||
return mail.subject
|
return mail.subject
|
||||||
|
|
||||||
def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
|
def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
|
||||||
""" Return a specific ir_email body. The main purpose of this method
|
"""Return a specific ir_email body. The main purpose of this method
|
||||||
is to be inherited to add custom content depending on some module.
|
is to be inherited to add custom content depending on some module."""
|
||||||
|
|
||||||
:param browse_record mail: mail.mail browse_record
|
|
||||||
:param browse_record partner: specific recipient partner
|
|
||||||
"""
|
|
||||||
body = mail.body_html
|
body = mail.body_html
|
||||||
|
|
||||||
# generate footer
|
# generate footer
|
||||||
|
@ -194,34 +188,34 @@ class mail_mail(osv.Model):
|
||||||
body = tools.append_content_to_html(body, link, plaintext=False, container_tag='div')
|
body = tools.append_content_to_html(body, link, plaintext=False, container_tag='div')
|
||||||
return body
|
return body
|
||||||
|
|
||||||
def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
|
def send_get_mail_to(self, cr, uid, mail, partner=None, context=None):
|
||||||
""" Return a dictionary for specific email values, depending on a
|
"""Forge the email_to with the following heuristic:
|
||||||
partner, or generic to the whole recipients given by mail.email_to.
|
- if 'partner' and mail is a notification on a document: followers (Followers of 'Doc' <email>)
|
||||||
|
- elif 'partner', no notificatoin or no doc: recipient specific (Partner Name <email>)
|
||||||
:param browse_record mail: mail.mail browse_record
|
- else fallback on mail.email_to splitting """
|
||||||
:param browse_record partner: specific recipient partner
|
if partner and mail.notification and mail.record_name:
|
||||||
"""
|
|
||||||
body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context)
|
|
||||||
subject = self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context)
|
|
||||||
body_alternative = tools.html2plaintext(body)
|
|
||||||
|
|
||||||
# generate email_to, heuristic:
|
|
||||||
# 1. if 'partner' is specified and there is a related document: Followers of 'Doc' <email>
|
|
||||||
# 2. if 'partner' is specified, but no related document: Partner Name <email>
|
|
||||||
# 3; fallback on mail.email_to that we split to have an email addresses list
|
|
||||||
if partner and mail.record_name:
|
|
||||||
sanitized_record_name = re.sub(r'[^\w+.]+', '-', mail.record_name)
|
sanitized_record_name = re.sub(r'[^\w+.]+', '-', mail.record_name)
|
||||||
email_to = [_('"Followers of %s" <%s>') % (sanitized_record_name, partner.email)]
|
email_to = [_('"Followers of %s" <%s>') % (sanitized_record_name, partner.email)]
|
||||||
elif partner:
|
elif partner:
|
||||||
email_to = ['%s <%s>' % (partner.name, partner.email)]
|
email_to = ['%s <%s>' % (partner.name, partner.email)]
|
||||||
else:
|
else:
|
||||||
email_to = tools.email_split(mail.email_to)
|
email_to = tools.email_split(mail.email_to)
|
||||||
|
return email_to
|
||||||
|
|
||||||
|
def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
|
||||||
|
"""Return a dictionary for specific email values, depending on a
|
||||||
|
partner, or generic to the whole recipients given by mail.email_to.
|
||||||
|
|
||||||
|
:param browse_record mail: mail.mail browse_record
|
||||||
|
:param browse_record partner: specific recipient partner
|
||||||
|
"""
|
||||||
|
body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context)
|
||||||
|
body_alternative = tools.html2plaintext(body)
|
||||||
return {
|
return {
|
||||||
'body': body,
|
'body': body,
|
||||||
'body_alternative': body_alternative,
|
'body_alternative': body_alternative,
|
||||||
'subject': subject,
|
'subject': self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context),
|
||||||
'email_to': email_to,
|
'email_to': self.send_get_mail_to(cr, uid, mail, partner=partner, context=context),
|
||||||
}
|
}
|
||||||
|
|
||||||
def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None):
|
def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None):
|
||||||
|
@ -240,7 +234,7 @@ class mail_mail(osv.Model):
|
||||||
:return: True
|
:return: True
|
||||||
"""
|
"""
|
||||||
ir_mail_server = self.pool.get('ir.mail_server')
|
ir_mail_server = self.pool.get('ir.mail_server')
|
||||||
|
|
||||||
for mail in self.browse(cr, SUPERUSER_ID, ids, context=context):
|
for mail in self.browse(cr, SUPERUSER_ID, ids, context=context):
|
||||||
try:
|
try:
|
||||||
# handle attachments
|
# handle attachments
|
||||||
|
@ -284,7 +278,7 @@ class mail_mail(osv.Model):
|
||||||
res = ir_mail_server.send_email(cr, uid, msg,
|
res = ir_mail_server.send_email(cr, uid, msg,
|
||||||
mail_server_id=mail.mail_server_id.id,
|
mail_server_id=mail.mail_server_id.id,
|
||||||
context=context)
|
context=context)
|
||||||
|
|
||||||
if res:
|
if res:
|
||||||
mail.write({'state': 'sent', 'message_id': res})
|
mail.write({'state': 'sent', 'message_id': res})
|
||||||
mail_sent = True
|
mail_sent = True
|
||||||
|
@ -294,11 +288,11 @@ class mail_mail(osv.Model):
|
||||||
|
|
||||||
# /!\ can't use mail.state here, as mail.refresh() will cause an error
|
# /!\ can't use mail.state here, as mail.refresh() will cause an error
|
||||||
# see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1
|
# see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1
|
||||||
if mail_sent:
|
self._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=mail_sent)
|
||||||
self._postprocess_sent_message(cr, uid, mail, context=context)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_logger.exception('failed sending mail.mail %s', mail.id)
|
_logger.exception('failed sending mail.mail %s', mail.id)
|
||||||
mail.write({'state': 'exception'})
|
mail.write({'state': 'exception'})
|
||||||
|
self._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=False)
|
||||||
if raise_exception:
|
if raise_exception:
|
||||||
if isinstance(e, AssertionError):
|
if isinstance(e, AssertionError):
|
||||||
# get the args of the original error, wrap into a value and throw a MailDeliveryException
|
# get the args of the original error, wrap into a value and throw a MailDeliveryException
|
||||||
|
@ -307,6 +301,6 @@ class mail_mail(osv.Model):
|
||||||
raise MailDeliveryException(_("Mail Delivery Failed"), value)
|
raise MailDeliveryException(_("Mail Delivery Failed"), value)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if auto_commit == True:
|
if auto_commit is True:
|
||||||
cr.commit()
|
cr.commit()
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
<div>
|
<div>
|
||||||
<group string="Status">
|
<group string="Status">
|
||||||
<field name="auto_delete"/>
|
<field name="auto_delete"/>
|
||||||
|
<field name="notification"/>
|
||||||
<field name="type"/>
|
<field name="type"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
<field name="mail_server_id"/>
|
<field name="mail_server_id"/>
|
||||||
|
|
|
@ -81,22 +81,6 @@ class mail_message(osv.Model):
|
||||||
context = dict(context, default_type=None)
|
context = dict(context, default_type=None)
|
||||||
return super(mail_message, self).default_get(cr, uid, fields, context=context)
|
return super(mail_message, self).default_get(cr, uid, fields, context=context)
|
||||||
|
|
||||||
def _shorten_name(self, name):
|
|
||||||
if len(name) <= (self._message_record_name_length + 3):
|
|
||||||
return name
|
|
||||||
return name[:self._message_record_name_length] + '...'
|
|
||||||
|
|
||||||
def _get_record_name(self, cr, uid, ids, name, arg, context=None):
|
|
||||||
""" Return the related document name, using name_get. It is done using
|
|
||||||
SUPERUSER_ID, to be sure to have the record name correctly stored. """
|
|
||||||
# TDE note: regroup by model/ids, to have less queries to perform
|
|
||||||
result = dict.fromkeys(ids, False)
|
|
||||||
for message in self.read(cr, uid, ids, ['model', 'res_id'], context=context):
|
|
||||||
if not message.get('model') or not message.get('res_id') or message['model'] not in self.pool:
|
|
||||||
continue
|
|
||||||
result[message['id']] = self.pool[message['model']].name_get(cr, SUPERUSER_ID, [message['res_id']], context=context)[0][1]
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _get_to_read(self, cr, uid, ids, name, arg, context=None):
|
def _get_to_read(self, cr, uid, ids, name, arg, context=None):
|
||||||
""" Compute if the message is unread by the current user. """
|
""" Compute if the message is unread by the current user. """
|
||||||
res = dict((id, False) for id in ids)
|
res = dict((id, False) for id in ids)
|
||||||
|
@ -135,16 +119,6 @@ class mail_message(osv.Model):
|
||||||
inversed because we search unread message on a read column. """
|
inversed because we search unread message on a read column. """
|
||||||
return ['&', ('notification_ids.partner_id.user_ids', 'in', [uid]), ('notification_ids.starred', '=', domain[0][2])]
|
return ['&', ('notification_ids.partner_id.user_ids', 'in', [uid]), ('notification_ids.starred', '=', domain[0][2])]
|
||||||
|
|
||||||
def name_get(self, cr, uid, ids, context=None):
|
|
||||||
# name_get may receive int id instead of an id list
|
|
||||||
if isinstance(ids, (int, long)):
|
|
||||||
ids = [ids]
|
|
||||||
res = []
|
|
||||||
for message in self.browse(cr, uid, ids, context=context):
|
|
||||||
name = '%s: %s' % (message.subject or '', strip_tags(message.body or '') or '')
|
|
||||||
res.append((message.id, self._shorten_name(name.lstrip(' :'))))
|
|
||||||
return res
|
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'type': fields.selection([
|
'type': fields.selection([
|
||||||
('email', 'Email'),
|
('email', 'Email'),
|
||||||
|
@ -172,9 +146,7 @@ class mail_message(osv.Model):
|
||||||
'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'),
|
'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'),
|
||||||
'model': fields.char('Related Document Model', size=128, select=1),
|
'model': fields.char('Related Document Model', size=128, select=1),
|
||||||
'res_id': fields.integer('Related Document ID', select=1),
|
'res_id': fields.integer('Related Document ID', select=1),
|
||||||
'record_name': fields.function(_get_record_name, type='char',
|
'record_name': fields.char('Message Record Name', help="Name get of the related document."),
|
||||||
store=True, string='Message Record Name',
|
|
||||||
help="Name get of the related document."),
|
|
||||||
'notification_ids': fields.one2many('mail.notification', 'message_id',
|
'notification_ids': fields.one2many('mail.notification', 'message_id',
|
||||||
string='Notifications', auto_join=True,
|
string='Notifications', auto_join=True,
|
||||||
help='Technical field holding the message notifications. Use notified_partner_ids to access notified partners.'),
|
help='Technical field holding the message notifications. Use notified_partner_ids to access notified partners.'),
|
||||||
|
@ -783,6 +755,13 @@ class mail_message(osv.Model):
|
||||||
_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \
|
_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \
|
||||||
(self._description, operation))
|
(self._description, operation))
|
||||||
|
|
||||||
|
def _get_record_name(self, cr, uid, values, context=None):
|
||||||
|
""" Return the related document name, using name_get. It is done using
|
||||||
|
SUPERUSER_ID, to be sure to have the record name correctly stored. """
|
||||||
|
if not values.get('model') or not values.get('res_id') or values['model'] not in self.pool:
|
||||||
|
return False
|
||||||
|
return self.pool[values['model']].name_get(cr, SUPERUSER_ID, [values['res_id']], context=context)[0][1]
|
||||||
|
|
||||||
def _get_reply_to(self, cr, uid, values, context=None):
|
def _get_reply_to(self, cr, uid, values, context=None):
|
||||||
""" Return a specific reply_to: alias of the document through message_get_reply_to
|
""" Return a specific reply_to: alias of the document through message_get_reply_to
|
||||||
or take the email_from
|
or take the email_from
|
||||||
|
@ -841,8 +820,11 @@ class mail_message(osv.Model):
|
||||||
values['message_id'] = self._get_message_id(cr, uid, values, context=context)
|
values['message_id'] = self._get_message_id(cr, uid, values, context=context)
|
||||||
if 'reply_to' not in values:
|
if 'reply_to' not in values:
|
||||||
values['reply_to'] = self._get_reply_to(cr, uid, values, context=context)
|
values['reply_to'] = self._get_reply_to(cr, uid, values, context=context)
|
||||||
|
if 'record_name' not in values and 'default_record_name' not in context:
|
||||||
|
values['record_name'] = self._get_record_name(cr, uid, values, context=context)
|
||||||
|
|
||||||
newid = super(mail_message, self).create(cr, uid, values, context)
|
newid = super(mail_message, self).create(cr, uid, values, context)
|
||||||
|
|
||||||
self._notify(cr, uid, newid, context=context,
|
self._notify(cr, uid, newid, context=context,
|
||||||
force_send=context.get('mail_notify_force_send', True),
|
force_send=context.get('mail_notify_force_send', True),
|
||||||
user_signature=context.get('mail_notify_user_signature', True))
|
user_signature=context.get('mail_notify_user_signature', True))
|
||||||
|
@ -887,78 +869,6 @@ class mail_message(osv.Model):
|
||||||
# Messaging API
|
# Messaging API
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
|
|
||||||
# TDE note: this code is not used currently, will be improved in a future merge, when quoted context
|
|
||||||
# will be added to email send for notifications. Currently only WIP.
|
|
||||||
MAIL_TEMPLATE = """<div>
|
|
||||||
% if message:
|
|
||||||
${display_message(message)}
|
|
||||||
% endif
|
|
||||||
% for ctx_msg in context_messages:
|
|
||||||
${display_message(ctx_msg)}
|
|
||||||
% endfor
|
|
||||||
% if add_expandable:
|
|
||||||
${display_expandable()}
|
|
||||||
% endif
|
|
||||||
${display_message(header_message)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%def name="display_message(message)">
|
|
||||||
<div>
|
|
||||||
Subject: ${message.subject}<br />
|
|
||||||
Body: ${message.body}
|
|
||||||
</div>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="display_expandable()">
|
|
||||||
<div>This is an expandable.</div>
|
|
||||||
</%def>
|
|
||||||
"""
|
|
||||||
|
|
||||||
def message_quote_context(self, cr, uid, id, context=None, limit=3, add_original=False):
|
|
||||||
"""
|
|
||||||
1. message.parent_id = False: new thread, no quote_context
|
|
||||||
2. get the lasts messages in the thread before message
|
|
||||||
3. get the message header
|
|
||||||
4. add an expandable between them
|
|
||||||
|
|
||||||
:param dict quote_context: options for quoting
|
|
||||||
:return string: html quote
|
|
||||||
"""
|
|
||||||
add_expandable = False
|
|
||||||
|
|
||||||
message = self.browse(cr, uid, id, context=context)
|
|
||||||
if not message.parent_id:
|
|
||||||
return ''
|
|
||||||
context_ids = self.search(cr, uid, [
|
|
||||||
('parent_id', '=', message.parent_id.id),
|
|
||||||
('id', '<', message.id),
|
|
||||||
], limit=limit, context=context)
|
|
||||||
|
|
||||||
if len(context_ids) >= limit:
|
|
||||||
add_expandable = True
|
|
||||||
context_ids = context_ids[0:-1]
|
|
||||||
|
|
||||||
context_ids.append(message.parent_id.id)
|
|
||||||
context_messages = self.browse(cr, uid, context_ids, context=context)
|
|
||||||
header_message = context_messages.pop()
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not add_original:
|
|
||||||
message = False
|
|
||||||
result = MakoTemplate(self.MAIL_TEMPLATE).render_unicode(message=message,
|
|
||||||
context_messages=context_messages,
|
|
||||||
header_message=header_message,
|
|
||||||
add_expandable=add_expandable,
|
|
||||||
# context kw would clash with mako internals
|
|
||||||
ctx=context,
|
|
||||||
format_exceptions=True)
|
|
||||||
result = result.strip()
|
|
||||||
return result
|
|
||||||
except Exception:
|
|
||||||
_logger.exception("failed to render mako template for quoting message")
|
|
||||||
return ''
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _notify(self, cr, uid, newid, context=None, force_send=False, user_signature=True):
|
def _notify(self, cr, uid, newid, context=None, force_send=False, user_signature=True):
|
||||||
""" Add the related record followers to the destination partner_ids if is not a private message.
|
""" Add the related record followers to the destination partner_ids if is not a private message.
|
||||||
Call mail_notification.notify to manage the email sending
|
Call mail_notification.notify to manage the email sending
|
||||||
|
@ -975,9 +885,11 @@ class mail_message(osv.Model):
|
||||||
cr, SUPERUSER_ID, [
|
cr, SUPERUSER_ID, [
|
||||||
('res_model', '=', message.model),
|
('res_model', '=', message.model),
|
||||||
('res_id', '=', message.res_id),
|
('res_id', '=', message.res_id),
|
||||||
('subtype_ids', 'in', message.subtype_id.id)
|
|
||||||
], context=context)
|
], context=context)
|
||||||
partners_to_notify |= set(fo.partner_id.id for fo in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context))
|
partners_to_notify |= set(
|
||||||
|
fo.partner_id.id for fo in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)
|
||||||
|
if message.subtype_id.id in [st.id for st in fo.subtype_ids]
|
||||||
|
)
|
||||||
# remove me from notified partners, unless the message is written on my own wall
|
# remove me from notified partners, unless the message is written on my own wall
|
||||||
if message.subtype_id and message.author_id and message.model == "res.partner" and message.res_id == message.author_id.id:
|
if message.subtype_id and message.author_id and message.model == "res.partner" and message.res_id == message.author_id.id:
|
||||||
partners_to_notify |= set([message.author_id.id])
|
partners_to_notify |= set([message.author_id.id])
|
||||||
|
@ -1006,25 +918,3 @@ class mail_message(osv.Model):
|
||||||
'partner_id': partner.id,
|
'partner_id': partner.id,
|
||||||
'read': True,
|
'read': True,
|
||||||
}, context=context)
|
}, context=context)
|
||||||
|
|
||||||
#------------------------------------------------------
|
|
||||||
# Tools
|
|
||||||
#------------------------------------------------------
|
|
||||||
|
|
||||||
def check_partners_email(self, cr, uid, partner_ids, context=None):
|
|
||||||
""" Verify that selected partner_ids have an email_address defined.
|
|
||||||
Otherwise throw a warning. """
|
|
||||||
partner_wo_email_lst = []
|
|
||||||
for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
|
|
||||||
if not partner.email:
|
|
||||||
partner_wo_email_lst.append(partner)
|
|
||||||
if not partner_wo_email_lst:
|
|
||||||
return {}
|
|
||||||
warning_msg = _('The following partners chosen as recipients for the email have no email address linked :')
|
|
||||||
for partner in partner_wo_email_lst:
|
|
||||||
warning_msg += '\n- %s' % (partner.name)
|
|
||||||
return {'warning': {
|
|
||||||
'title': _('Partners email addresses not found'),
|
|
||||||
'message': warning_msg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ except ImportError:
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
import logging
|
import logging
|
||||||
import pytz
|
import pytz
|
||||||
|
import socket
|
||||||
import time
|
import time
|
||||||
import xmlrpclib
|
import xmlrpclib
|
||||||
from email.message import Message
|
from email.message import Message
|
||||||
|
@ -96,6 +97,9 @@ class mail_thread(osv.AbstractModel):
|
||||||
# :param function lambda: returns whether the tracking should record using this subtype
|
# :param function lambda: returns whether the tracking should record using this subtype
|
||||||
_track = {}
|
_track = {}
|
||||||
|
|
||||||
|
# Mass mailing feature
|
||||||
|
_mail_mass_mailing = False
|
||||||
|
|
||||||
def get_empty_list_help(self, cr, uid, help, context=None):
|
def get_empty_list_help(self, cr, uid, help, context=None):
|
||||||
""" Override of BaseModel.get_empty_list_help() to generate an help message
|
""" Override of BaseModel.get_empty_list_help() to generate an help message
|
||||||
that adds alias information. """
|
that adds alias information. """
|
||||||
|
@ -584,23 +588,6 @@ class mail_thread(osv.AbstractModel):
|
||||||
model_obj.check_access_rights(cr, uid, check_operation)
|
model_obj.check_access_rights(cr, uid, check_operation)
|
||||||
model_obj.check_access_rule(cr, uid, mids, check_operation, context=context)
|
model_obj.check_access_rule(cr, uid, mids, check_operation, context=context)
|
||||||
|
|
||||||
def _get_formview_action(self, cr, uid, id, model=None, context=None):
|
|
||||||
""" Return an action to open the document. This method is meant to be
|
|
||||||
overridden in addons that want to give specific view ids for example.
|
|
||||||
|
|
||||||
:param int id: id of the document to open
|
|
||||||
:param string model: specific model that overrides self._name
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
'type': 'ir.actions.act_window',
|
|
||||||
'res_model': model or self._name,
|
|
||||||
'view_type': 'form',
|
|
||||||
'view_mode': 'form',
|
|
||||||
'views': [(False, 'form')],
|
|
||||||
'target': 'current',
|
|
||||||
'res_id': id,
|
|
||||||
}
|
|
||||||
|
|
||||||
def _get_inbox_action_xml_id(self, cr, uid, context=None):
|
def _get_inbox_action_xml_id(self, cr, uid, context=None):
|
||||||
""" When redirecting towards the Inbox, choose which action xml_id has
|
""" When redirecting towards the Inbox, choose which action xml_id has
|
||||||
to be fetched. This method is meant to be inherited, at least in portal
|
to be fetched. This method is meant to be inherited, at least in portal
|
||||||
|
@ -643,10 +630,7 @@ class mail_thread(osv.AbstractModel):
|
||||||
if model_obj.check_access_rights(cr, uid, 'read', raise_exception=False):
|
if model_obj.check_access_rights(cr, uid, 'read', raise_exception=False):
|
||||||
try:
|
try:
|
||||||
model_obj.check_access_rule(cr, uid, [res_id], 'read', context=context)
|
model_obj.check_access_rule(cr, uid, [res_id], 'read', context=context)
|
||||||
if not hasattr(model_obj, '_get_formview_action'):
|
action = model_obj.get_formview_action(cr, uid, res_id, context=context)
|
||||||
action = self.pool.get('mail.thread')._get_formview_action(cr, uid, res_id, model=model, context=context)
|
|
||||||
else:
|
|
||||||
action = model_obj._get_formview_action(cr, uid, res_id, context=context)
|
|
||||||
except (osv.except_osv, orm.except_orm):
|
except (osv.except_osv, orm.except_orm):
|
||||||
pass
|
pass
|
||||||
action.update({
|
action.update({
|
||||||
|
@ -661,15 +645,31 @@ class mail_thread(osv.AbstractModel):
|
||||||
# Email specific
|
# Email specific
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
|
|
||||||
|
def message_get_default_recipients(self, cr, uid, ids, context=None):
|
||||||
|
if context and context.get('thread_model') and context['thread_model'] in self.pool and context['thread_model'] != self._name:
|
||||||
|
sub_ctx = dict(context)
|
||||||
|
sub_ctx.pop('thread_model')
|
||||||
|
return self.pool[context['thread_model']].message_get_default_recipients(cr, uid, ids, context=sub_ctx)
|
||||||
|
res = {}
|
||||||
|
for record in self.browse(cr, SUPERUSER_ID, ids, context=context):
|
||||||
|
recipient_ids, email_to, email_cc = set(), False, False
|
||||||
|
if 'partner_id' in self._all_columns and record.partner_id:
|
||||||
|
recipient_ids.add(record.partner_id.id)
|
||||||
|
elif 'email_from' in self._all_columns and record.email_from:
|
||||||
|
email_to = record.email_from
|
||||||
|
elif 'email' in self._all_columns:
|
||||||
|
email_to = record.email
|
||||||
|
res[record.id] = {'partner_ids': list(recipient_ids), 'email_to': email_to, 'email_cc': email_cc}
|
||||||
|
return res
|
||||||
|
|
||||||
def message_get_reply_to(self, cr, uid, ids, context=None):
|
def message_get_reply_to(self, cr, uid, ids, context=None):
|
||||||
""" Returns the preferred reply-to email address that is basically
|
""" Returns the preferred reply-to email address that is basically
|
||||||
the alias of the document, if it exists. """
|
the alias of the document, if it exists. """
|
||||||
if not self._inherits.get('mail.alias'):
|
if not self._inherits.get('mail.alias'):
|
||||||
return [False for id in ids]
|
return [False for id in ids]
|
||||||
return ["%s@%s" % (record['alias_name'], record['alias_domain'])
|
return ["%s@%s" % (record.alias_name, record.alias_domain)
|
||||||
if record.get('alias_domain') and record.get('alias_name')
|
if record.alias_domain and record.alias_name else False
|
||||||
else False
|
for record in self.browse(cr, SUPERUSER_ID, ids, context=context)]
|
||||||
for record in self.read(cr, SUPERUSER_ID, ids, ['alias_name', 'alias_domain'], context=context)]
|
|
||||||
|
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
# Mail gateway
|
# Mail gateway
|
||||||
|
@ -880,25 +880,30 @@ class mail_thread(osv.AbstractModel):
|
||||||
# 2. message is a reply to an existign thread (6.1 compatibility)
|
# 2. message is a reply to an existign thread (6.1 compatibility)
|
||||||
ref_match = thread_references and tools.reference_re.search(thread_references)
|
ref_match = thread_references and tools.reference_re.search(thread_references)
|
||||||
if ref_match:
|
if ref_match:
|
||||||
thread_id = int(ref_match.group(1))
|
reply_thread_id = int(ref_match.group(1))
|
||||||
model = ref_match.group(2) or fallback_model
|
reply_model = ref_match.group(2) or fallback_model
|
||||||
if thread_id and model in self.pool:
|
reply_hostname = ref_match.group(3)
|
||||||
model_obj = self.pool[model]
|
local_hostname = socket.gethostname()
|
||||||
compat_mail_msg_ids = mail_msg_obj.search(
|
# do not match forwarded emails from another OpenERP system (thread_id collision!)
|
||||||
cr, uid, [
|
if local_hostname == reply_hostname:
|
||||||
('message_id', '=', False),
|
thread_id, model = reply_thread_id, reply_model
|
||||||
('model', '=', model),
|
if thread_id and model in self.pool:
|
||||||
('res_id', '=', thread_id),
|
model_obj = self.pool[model]
|
||||||
], context=context)
|
compat_mail_msg_ids = mail_msg_obj.search(
|
||||||
if compat_mail_msg_ids and model_obj.exists(cr, uid, thread_id) and hasattr(model_obj, 'message_update'):
|
cr, uid, [
|
||||||
_logger.info(
|
('message_id', '=', False),
|
||||||
'Routing mail from %s to %s with Message-Id %s: direct thread reply (compat-mode) to model: %s, thread_id: %s, custom_values: %s, uid: %s',
|
('model', '=', model),
|
||||||
email_from, email_to, message_id, model, thread_id, custom_values, uid)
|
('res_id', '=', thread_id),
|
||||||
route = self.message_route_verify(
|
], context=context)
|
||||||
cr, uid, message, message_dict,
|
if compat_mail_msg_ids and model_obj.exists(cr, uid, thread_id) and hasattr(model_obj, 'message_update'):
|
||||||
(model, thread_id, custom_values, uid, None),
|
_logger.info(
|
||||||
update_author=True, assert_model=True, create_fallback=True, context=context)
|
'Routing mail from %s to %s with Message-Id %s: direct thread reply (compat-mode) to model: %s, thread_id: %s, custom_values: %s, uid: %s',
|
||||||
return route and [route] or []
|
email_from, email_to, message_id, model, thread_id, custom_values, uid)
|
||||||
|
route = self.message_route_verify(
|
||||||
|
cr, uid, message, message_dict,
|
||||||
|
(model, thread_id, custom_values, uid, None),
|
||||||
|
update_author=True, assert_model=True, create_fallback=True, context=context)
|
||||||
|
return route and [route] or []
|
||||||
|
|
||||||
# 2. Reply to a private message
|
# 2. Reply to a private message
|
||||||
if in_reply_to:
|
if in_reply_to:
|
||||||
|
|
|
@ -28,6 +28,7 @@ class res_partner_mail(osv.Model):
|
||||||
_name = "res.partner"
|
_name = "res.partner"
|
||||||
_inherit = ['res.partner', 'mail.thread']
|
_inherit = ['res.partner', 'mail.thread']
|
||||||
_mail_flat_thread = False
|
_mail_flat_thread = False
|
||||||
|
_mail_mass_mailing = _('Customers')
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'notification_email_send': fields.selection([
|
'notification_email_send': fields.selection([
|
||||||
|
@ -53,4 +54,5 @@ class res_partner_mail(osv.Model):
|
||||||
self._message_add_suggested_recipient(cr, uid, recipients, partner, partner=partner, reason=_('Partner Profile'))
|
self._message_add_suggested_recipient(cr, uid, recipients, partner, partner=partner, reason=_('Partner Profile'))
|
||||||
return recipients
|
return recipients
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
def message_get_default_recipients(self, cr, uid, ids, context=None):
|
||||||
|
return dict((id, {'partner_ids': [id], 'email_to': False, 'email_cc': False}) for id in ids)
|
||||||
|
|
|
@ -507,18 +507,15 @@ openerp.mail = function (session) {
|
||||||
}
|
}
|
||||||
$.when(recipient_done).done(function (partner_ids) {
|
$.when(recipient_done).done(function (partner_ids) {
|
||||||
var context = {
|
var context = {
|
||||||
'default_composition_mode': default_composition_mode,
|
|
||||||
'default_parent_id': self.id,
|
'default_parent_id': self.id,
|
||||||
'default_body': mail.ChatterUtils.get_text2html(self.$el ? (self.$el.find('textarea:not(.oe_compact)').val() || '') : ''),
|
'default_body': mail.ChatterUtils.get_text2html(self.$el ? (self.$el.find('textarea:not(.oe_compact)').val() || '') : ''),
|
||||||
'default_attachment_ids': _.map(self.attachment_ids, function (file) {return file.id;}),
|
'default_attachment_ids': _.map(self.attachment_ids, function (file) {return file.id;}),
|
||||||
'default_partner_ids': partner_ids,
|
'default_partner_ids': partner_ids,
|
||||||
|
'default_is_log': self.is_log,
|
||||||
'mail_post_autofollow': true,
|
'mail_post_autofollow': true,
|
||||||
'mail_post_autofollow_partner_ids': partner_ids,
|
'mail_post_autofollow_partner_ids': partner_ids,
|
||||||
'is_private': self.is_private
|
'is_private': self.is_private
|
||||||
};
|
};
|
||||||
if (self.is_log) {
|
|
||||||
_.extend(context, {'mail_compose_log': true});
|
|
||||||
}
|
|
||||||
if (default_composition_mode != 'reply' && self.context.default_model && self.context.default_res_id) {
|
if (default_composition_mode != 'reply' && self.context.default_model && self.context.default_res_id) {
|
||||||
context.default_model = self.context.default_model;
|
context.default_model = self.context.default_model;
|
||||||
context.default_res_id = self.context.default_res_id;
|
context.default_res_id = self.context.default_res_id;
|
||||||
|
|
|
@ -210,24 +210,6 @@ class test_mail(TestMail):
|
||||||
self.assertTrue(subtype_data['mt_mg_nodef']['followed'], 'Admin should follow mt_mg_nodef in pigs')
|
self.assertTrue(subtype_data['mt_mg_nodef']['followed'], 'Admin should follow mt_mg_nodef in pigs')
|
||||||
self.assertTrue(subtype_data['mt_all_nodef']['followed'], 'Admin should follow mt_all_nodef in pigs')
|
self.assertTrue(subtype_data['mt_all_nodef']['followed'], 'Admin should follow mt_all_nodef in pigs')
|
||||||
|
|
||||||
def test_10_message_quote_context(self):
|
|
||||||
""" Tests designed for message_post. """
|
|
||||||
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
|
|
||||||
|
|
||||||
msg1_id = self.mail_message.create(cr, uid, {'body': 'Thread header about Zap Brannigan', 'subject': 'My subject'})
|
|
||||||
msg2_id = self.mail_message.create(cr, uid, {'body': 'First answer, should not be displayed', 'subject': 'Re: My subject', 'parent_id': msg1_id})
|
|
||||||
msg3_id = self.mail_message.create(cr, uid, {'body': 'Second answer', 'subject': 'Re: My subject', 'parent_id': msg1_id})
|
|
||||||
msg4_id = self.mail_message.create(cr, uid, {'body': 'Third answer', 'subject': 'Re: My subject', 'parent_id': msg1_id})
|
|
||||||
msg_new_id = self.mail_message.create(cr, uid, {'body': 'My answer I am propagating', 'subject': 'Re: My subject', 'parent_id': msg1_id})
|
|
||||||
|
|
||||||
result = self.mail_message.message_quote_context(cr, uid, msg_new_id, limit=3)
|
|
||||||
self.assertIn('Thread header about Zap Brannigan', result, 'Thread header content should be in quote.')
|
|
||||||
self.assertIn('Second answer', result, 'Answer should be in quote.')
|
|
||||||
self.assertIn('Third answer', result, 'Answer should be in quote.')
|
|
||||||
self.assertIn('expandable', result, 'Expandable should be present.')
|
|
||||||
self.assertNotIn('First answer, should not be displayed', result, 'Old answer should not be in quote.')
|
|
||||||
self.assertNotIn('My answer I am propagating', result, 'Thread header content should be in quote.')
|
|
||||||
|
|
||||||
def test_11_notification_url(self):
|
def test_11_notification_url(self):
|
||||||
""" Tests designed to test the URL added in notification emails. """
|
""" Tests designed to test the URL added in notification emails. """
|
||||||
cr, uid, group_pigs = self.cr, self.uid, self.group_pigs
|
cr, uid, group_pigs = self.cr, self.uid, self.group_pigs
|
||||||
|
@ -674,7 +656,6 @@ class test_mail(TestMail):
|
||||||
'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]
|
'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]
|
||||||
}, context={
|
}, context={
|
||||||
'default_composition_mode': 'reply',
|
'default_composition_mode': 'reply',
|
||||||
'default_model': 'mail.thread',
|
|
||||||
'default_res_id': self.group_pigs_id,
|
'default_res_id': self.group_pigs_id,
|
||||||
'default_parent_id': message.id
|
'default_parent_id': message.id
|
||||||
})
|
})
|
||||||
|
@ -699,11 +680,10 @@ class test_mail(TestMail):
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
# Do: Compose in mass_mail_mode on pigs and bird
|
# Do: Compose in mass_mail_mode on pigs and bird
|
||||||
compose_id = mail_compose.create(cr, user_raoul.id,
|
compose_id = mail_compose.create(
|
||||||
{
|
cr, user_raoul.id, {
|
||||||
'subject': _subject,
|
'subject': _subject,
|
||||||
'body': '${object.description}',
|
'body': '${object.description}',
|
||||||
'post': True,
|
|
||||||
'partner_ids': [(4, p_c_id), (4, p_d_id)],
|
'partner_ids': [(4, p_c_id), (4, p_d_id)],
|
||||||
}, context={
|
}, context={
|
||||||
'default_composition_mode': 'mass_mail',
|
'default_composition_mode': 'mass_mail',
|
||||||
|
@ -718,6 +698,13 @@ class test_mail(TestMail):
|
||||||
'default_res_id': -1,
|
'default_res_id': -1,
|
||||||
'active_ids': [self.group_pigs_id, group_bird_id]
|
'active_ids': [self.group_pigs_id, group_bird_id]
|
||||||
})
|
})
|
||||||
|
# check mail_mail
|
||||||
|
mail_mail_ids = self.mail_mail.search(cr, uid, [('subject', '=', _subject)])
|
||||||
|
for mail_mail in self.mail_mail.browse(cr, uid, mail_mail_ids):
|
||||||
|
self.assertEqual(set([p.id for p in mail_mail.recipient_ids]), set([p_c_id, p_d_id]),
|
||||||
|
'compose wizard: mail_mail mass mailing: mail.mail in mass mail incorrect recipients')
|
||||||
|
|
||||||
|
# check logged messages
|
||||||
group_pigs.refresh()
|
group_pigs.refresh()
|
||||||
group_bird.refresh()
|
group_bird.refresh()
|
||||||
message1 = group_pigs.message_ids[0]
|
message1 = group_pigs.message_ids[0]
|
||||||
|
@ -733,14 +720,14 @@ class test_mail(TestMail):
|
||||||
'compose wizard: message_post: mail.message in mass mail subject incorrect')
|
'compose wizard: message_post: mail.message in mass mail subject incorrect')
|
||||||
self.assertEqual(message1.body, '<p>%s</p>' % group_pigs.description,
|
self.assertEqual(message1.body, '<p>%s</p>' % group_pigs.description,
|
||||||
'compose wizard: message_post: mail.message in mass mail body incorrect')
|
'compose wizard: message_post: mail.message in mass mail body incorrect')
|
||||||
self.assertEqual(set([p.id for p in message1.notified_partner_ids]), set([p_c_id, p_d_id]),
|
# self.assertEqual(set([p.id for p in message1.notified_partner_ids]), set([p_c_id, p_d_id]),
|
||||||
'compose wizard: message_post: mail.message in mass mail incorrect notified partners')
|
# 'compose wizard: message_post: mail.message in mass mail incorrect notified partners')
|
||||||
self.assertEqual(message2.subject, _subject,
|
self.assertEqual(message2.subject, _subject,
|
||||||
'compose wizard: message_post: mail.message in mass mail subject incorrect')
|
'compose wizard: message_post: mail.message in mass mail subject incorrect')
|
||||||
self.assertEqual(message2.body, '<p>%s</p>' % group_bird.description,
|
self.assertEqual(message2.body, '<p>%s</p>' % group_bird.description,
|
||||||
'compose wizard: message_post: mail.message in mass mail body incorrect')
|
'compose wizard: message_post: mail.message in mass mail body incorrect')
|
||||||
self.assertEqual(set([p.id for p in message2.notified_partner_ids]), set([p_c_id, p_d_id]),
|
# self.assertEqual(set([p.id for p in message2.notified_partner_ids]), set([p_c_id, p_d_id]),
|
||||||
'compose wizard: message_post: mail.message in mass mail incorrect notified partners')
|
# 'compose wizard: message_post: mail.message in mass mail incorrect notified partners')
|
||||||
|
|
||||||
# Test: mail.group followers: author not added as follower in mass mail mode
|
# Test: mail.group followers: author not added as follower in mass mail mode
|
||||||
pigs_pids = [p.id for p in group_pigs.message_follower_ids]
|
pigs_pids = [p.id for p in group_pigs.message_follower_ids]
|
||||||
|
@ -757,7 +744,6 @@ class test_mail(TestMail):
|
||||||
{
|
{
|
||||||
'subject': _subject,
|
'subject': _subject,
|
||||||
'body': '${object.description}',
|
'body': '${object.description}',
|
||||||
'post': True,
|
|
||||||
'partner_ids': [(4, p_c_id), (4, p_d_id)],
|
'partner_ids': [(4, p_c_id), (4, p_d_id)],
|
||||||
}, context={
|
}, context={
|
||||||
'default_composition_mode': 'mass_mail',
|
'default_composition_mode': 'mass_mail',
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
from openerp.addons.mail.tests.common import TestMail
|
from openerp.addons.mail.tests.common import TestMail
|
||||||
from openerp.tools import mute_logger
|
from openerp.tools import mute_logger
|
||||||
|
import socket
|
||||||
|
|
||||||
MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
|
MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
|
||||||
To: {to}
|
To: {to}
|
||||||
|
@ -400,13 +401,15 @@ class TestMailgateway(TestMail):
|
||||||
to='noone@example.com', subject='spam',
|
to='noone@example.com', subject='spam',
|
||||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>' % frog_group.id,
|
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>' % frog_group.id,
|
||||||
msg_id='<1.1.JavaMail.new@agrolait.com>')
|
msg_id='<1.1.JavaMail.new@agrolait.com>')
|
||||||
# There are 6.1 messages, activate compat mode
|
|
||||||
|
# When 6.1 messages are present, compat mode is available
|
||||||
|
# Create a fake 6.1 message
|
||||||
tmp_msg_id = self.mail_message.create(cr, uid, {'message_id': False, 'model': 'mail.group', 'res_id': frog_group.id})
|
tmp_msg_id = self.mail_message.create(cr, uid, {'message_id': False, 'model': 'mail.group', 'res_id': frog_group.id})
|
||||||
# Do: compat mode accepts partial-matching emails
|
# Do: compat mode accepts partial-matching emails
|
||||||
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other5@gmail.com',
|
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other5@gmail.com',
|
||||||
msg_id='<1.2.JavaMail.new@agrolait.com>',
|
msg_id='<1.2.JavaMail.new@agrolait.com>',
|
||||||
to='noone@example.com>', subject='spam',
|
to='noone@example.com>', subject='spam',
|
||||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>' % frog_group.id)
|
extra='In-Reply-To: <12321321-openerp-%d-mail.group@%s>' % (frog_group.id, socket.gethostname()))
|
||||||
self.mail_message.unlink(cr, uid, [tmp_msg_id])
|
self.mail_message.unlink(cr, uid, [tmp_msg_id])
|
||||||
# Test: no group 'Re: news' created, still only 1 Frogs group
|
# Test: no group 'Re: news' created, still only 1 Frogs group
|
||||||
self.assertEqual(len(frog_groups), 0,
|
self.assertEqual(len(frog_groups), 0,
|
||||||
|
@ -418,6 +421,17 @@ class TestMailgateway(TestMail):
|
||||||
# Test: one new message
|
# Test: one new message
|
||||||
self.assertEqual(len(frog_group.message_ids), 4, 'message_process: group should contain 4 messages after reply')
|
self.assertEqual(len(frog_group.message_ids), 4, 'message_process: group should contain 4 messages after reply')
|
||||||
|
|
||||||
|
# 6.1 compat mode should not work if hostname does not match!
|
||||||
|
tmp_msg_id = self.mail_message.create(cr, uid, {'message_id': False, 'model': 'mail.group', 'res_id': frog_group.id})
|
||||||
|
self.assertRaises(ValueError,
|
||||||
|
format_and_process,
|
||||||
|
MAIL_TEMPLATE, email_from='other5@gmail.com',
|
||||||
|
msg_id='<1.3.JavaMail.new@agrolait.com>',
|
||||||
|
to='noone@example.com>', subject='spam',
|
||||||
|
extra='In-Reply-To: <12321321-openerp-%d-mail.group@neighbor.com>' % frog_group.id)
|
||||||
|
self.mail_message.unlink(cr, uid, [tmp_msg_id])
|
||||||
|
|
||||||
|
|
||||||
# Do: due to some issue, same email goes back into the mailgateway
|
# Do: due to some issue, same email goes back into the mailgateway
|
||||||
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
|
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
|
||||||
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
|
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
|
||||||
|
@ -445,7 +459,7 @@ class TestMailgateway(TestMail):
|
||||||
|
|
||||||
# Do: post a new message, with a known partner -> duplicate emails -> partner
|
# Do: post a new message, with a known partner -> duplicate emails -> partner
|
||||||
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
||||||
to='erroneous@example.com>', subject='Re: news (2)',
|
subject='Re: news (2)',
|
||||||
msg_id='<1198923581.41972151344608186760.JavaMail.new1@agrolait.com>',
|
msg_id='<1198923581.41972151344608186760.JavaMail.new1@agrolait.com>',
|
||||||
extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
|
extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
|
||||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||||
|
@ -456,10 +470,9 @@ class TestMailgateway(TestMail):
|
||||||
|
|
||||||
# Do: post a new message, with a known partner -> duplicate emails -> user
|
# Do: post a new message, with a known partner -> duplicate emails -> user
|
||||||
frog_group.message_unsubscribe([extra_partner_id])
|
frog_group.message_unsubscribe([extra_partner_id])
|
||||||
raoul_email = self.user_raoul.email
|
|
||||||
self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
|
self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
|
||||||
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
||||||
to='erroneous@example.com>', subject='Re: news (3)',
|
to='groups@example.com', subject='Re: news (3)',
|
||||||
msg_id='<1198923581.41972151344608186760.JavaMail.new2@agrolait.com>',
|
msg_id='<1198923581.41972151344608186760.JavaMail.new2@agrolait.com>',
|
||||||
extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
|
extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
|
||||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||||
|
@ -474,7 +487,7 @@ class TestMailgateway(TestMail):
|
||||||
raoul_email = self.user_raoul.email
|
raoul_email = self.user_raoul.email
|
||||||
self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
|
self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
|
||||||
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
||||||
to='erroneous@example.com>', subject='Re: news (3)',
|
to='groups@example.com', subject='Re: news (3)',
|
||||||
msg_id='<1198923581.41972151344608186760.JavaMail.new3@agrolait.com>',
|
msg_id='<1198923581.41972151344608186760.JavaMail.new3@agrolait.com>',
|
||||||
extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
|
extra='In-Reply-To: <1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>')
|
||||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||||
|
|
|
@ -38,10 +38,7 @@ class mail_compose_message(osv.TransientModel):
|
||||||
at model and view levels to provide specific features.
|
at model and view levels to provide specific features.
|
||||||
|
|
||||||
The behavior of the wizard depends on the composition_mode field:
|
The behavior of the wizard depends on the composition_mode field:
|
||||||
- 'reply': reply to a previous message. The wizard is pre-populated
|
- 'comment': post on a record. The wizard is pre-populated via ``get_record_data``
|
||||||
via ``get_message_data``.
|
|
||||||
- 'comment': new post on a record. The wizard is pre-populated via
|
|
||||||
``get_record_data``
|
|
||||||
- 'mass_mail': wizard in mass mailing mode where the mail details can
|
- 'mass_mail': wizard in mass mailing mode where the mail details can
|
||||||
contain template placeholders that will be merged with actual data
|
contain template placeholders that will be merged with actual data
|
||||||
before being sent to each recipient.
|
before being sent to each recipient.
|
||||||
|
@ -50,6 +47,7 @@ class mail_compose_message(osv.TransientModel):
|
||||||
_inherit = 'mail.message'
|
_inherit = 'mail.message'
|
||||||
_description = 'Email composition wizard'
|
_description = 'Email composition wizard'
|
||||||
_log_access = True
|
_log_access = True
|
||||||
|
_batch_size = 500
|
||||||
|
|
||||||
def default_get(self, cr, uid, fields, context=None):
|
def default_get(self, cr, uid, fields, context=None):
|
||||||
""" Handle composition mode. Some details about context keys:
|
""" Handle composition mode. Some details about context keys:
|
||||||
|
@ -68,28 +66,22 @@ class mail_compose_message(osv.TransientModel):
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
|
result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
|
||||||
# get some important values from context
|
|
||||||
composition_mode = context.get('default_composition_mode', context.get('mail.compose.message.mode'))
|
# v6.1 compatibility mode
|
||||||
model = context.get('default_model', context.get('active_model'))
|
result['composition_mode'] = result.get('composition_mode', context.get('mail.compose.message.mode'))
|
||||||
res_id = context.get('default_res_id', context.get('active_id'))
|
result['model'] = result.get('model', context.get('active_model'))
|
||||||
message_id = context.get('default_parent_id', context.get('message_id', context.get('active_id')))
|
result['res_id'] = result.get('res_id', context.get('active_id'))
|
||||||
active_ids = context.get('active_ids')
|
result['parent_id'] = result.get('parent_id', context.get('message_id'))
|
||||||
|
|
||||||
|
# default values according to composition mode - NOTE: reply is deprecated, fall back on comment
|
||||||
|
if result['composition_mode'] == 'reply':
|
||||||
|
result['composition_mode'] = 'comment'
|
||||||
|
vals = {}
|
||||||
if 'active_domain' in context: # not context.get() because we want to keep global [] domains
|
if 'active_domain' in context: # not context.get() because we want to keep global [] domains
|
||||||
result['use_active_domain'] = True
|
vals['use_active_domain'] = True
|
||||||
result['active_domain'] = '%s' % context.get('active_domain')
|
vals['active_domain'] = '%s' % context.get('active_domain')
|
||||||
elif not result.get('active_domain'):
|
if result['composition_mode'] == 'comment':
|
||||||
result['active_domain'] = ''
|
vals.update(self.get_record_data(cr, uid, result, context=context))
|
||||||
# get default values according to the composition mode
|
|
||||||
if composition_mode == 'reply':
|
|
||||||
vals = self.get_message_data(cr, uid, message_id, context=context)
|
|
||||||
elif composition_mode == 'comment' and model and res_id:
|
|
||||||
vals = self.get_record_data(cr, uid, model, res_id, context=context)
|
|
||||||
elif composition_mode == 'mass_mail' and model and active_ids:
|
|
||||||
vals = {'model': model, 'res_id': res_id}
|
|
||||||
else:
|
|
||||||
vals = {'model': model, 'res_id': res_id}
|
|
||||||
if composition_mode:
|
|
||||||
vals['composition_mode'] = composition_mode
|
|
||||||
|
|
||||||
for field in vals:
|
for field in vals:
|
||||||
if field in fields:
|
if field in fields:
|
||||||
|
@ -102,13 +94,15 @@ class mail_compose_message(osv.TransientModel):
|
||||||
# but when creating the mail.message to create the mail.compose.message
|
# but when creating the mail.message to create the mail.compose.message
|
||||||
# access rights issues may rise
|
# access rights issues may rise
|
||||||
# We therefore directly change the model and res_id
|
# We therefore directly change the model and res_id
|
||||||
if result.get('model') == 'res.users' and result.get('res_id') == uid:
|
if result['model'] == 'res.users' and result['res_id'] == uid:
|
||||||
result['model'] = 'res.partner'
|
result['model'] = 'res.partner'
|
||||||
result['res_id'] = self.pool.get('res.users').browse(cr, uid, uid).partner_id.id
|
result['res_id'] = self.pool.get('res.users').browse(cr, uid, uid).partner_id.id
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _get_composition_mode_selection(self, cr, uid, context=None):
|
def _get_composition_mode_selection(self, cr, uid, context=None):
|
||||||
return [('comment', 'Comment a document'), ('reply', 'Reply to a message'), ('mass_mail', 'Mass mailing')]
|
return [('comment', 'Post on a document'),
|
||||||
|
('mass_mail', 'Email Mass Mailing'),
|
||||||
|
('mass_post', 'Post on Multiple Documents')]
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
'composition_mode': fields.selection(
|
'composition_mode': fields.selection(
|
||||||
|
@ -116,19 +110,19 @@ class mail_compose_message(osv.TransientModel):
|
||||||
string='Composition mode'),
|
string='Composition mode'),
|
||||||
'partner_ids': fields.many2many('res.partner',
|
'partner_ids': fields.many2many('res.partner',
|
||||||
'mail_compose_message_res_partner_rel',
|
'mail_compose_message_res_partner_rel',
|
||||||
'wizard_id', 'partner_id', 'Additional contacts'),
|
'wizard_id', 'partner_id', 'Additional Contacts'),
|
||||||
'use_active_domain': fields.boolean('Use active domain'),
|
'use_active_domain': fields.boolean('Use active domain'),
|
||||||
'active_domain': fields.char('Active domain', readonly=True),
|
'active_domain': fields.char('Active domain', readonly=True),
|
||||||
'post': fields.boolean('Post a copy in the document',
|
|
||||||
help='Post a copy of the message on the document communication history.'),
|
|
||||||
'notify': fields.boolean('Notify followers',
|
|
||||||
help='Notify followers of the document'),
|
|
||||||
'same_thread': fields.boolean('Replies in the document',
|
|
||||||
help='Replies to the messages will go into the selected document.'),
|
|
||||||
'attachment_ids': fields.many2many('ir.attachment',
|
'attachment_ids': fields.many2many('ir.attachment',
|
||||||
'mail_compose_message_ir_attachments_rel',
|
'mail_compose_message_ir_attachments_rel',
|
||||||
'wizard_id', 'attachment_id', 'Attachments'),
|
'wizard_id', 'attachment_id', 'Attachments'),
|
||||||
'filter_id': fields.many2one('ir.filters', 'Filters'),
|
'is_log': fields.boolean('Log an Internal Note',
|
||||||
|
help='Whether the message is an internal note (comment mode only)'),
|
||||||
|
# mass mode options
|
||||||
|
'notify': fields.boolean('Notify followers',
|
||||||
|
help='Notify followers of the document (mass post only)'),
|
||||||
|
'same_thread': fields.boolean('Replies in the document',
|
||||||
|
help='Replies to the messages will go into the selected document (mass mail only)'),
|
||||||
}
|
}
|
||||||
#TODO change same_thread to False in trunk (Require view update)
|
#TODO change same_thread to False in trunk (Require view update)
|
||||||
_defaults = {
|
_defaults = {
|
||||||
|
@ -136,8 +130,6 @@ class mail_compose_message(osv.TransientModel):
|
||||||
'body': lambda self, cr, uid, ctx={}: '',
|
'body': lambda self, cr, uid, ctx={}: '',
|
||||||
'subject': lambda self, cr, uid, ctx={}: False,
|
'subject': lambda self, cr, uid, ctx={}: False,
|
||||||
'partner_ids': lambda self, cr, uid, ctx={}: [],
|
'partner_ids': lambda self, cr, uid, ctx={}: [],
|
||||||
'post': False,
|
|
||||||
'notify': False,
|
|
||||||
'same_thread': True,
|
'same_thread': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,61 +161,36 @@ class mail_compose_message(osv.TransientModel):
|
||||||
not want that feature in the wizard. """
|
not want that feature in the wizard. """
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_record_data(self, cr, uid, model, res_id, context=None):
|
def get_record_data(self, cr, uid, values, context=None):
|
||||||
""" Returns a defaults-like dict with initial values for the composition
|
""" Returns a defaults-like dict with initial values for the composition
|
||||||
wizard when sending an email related to the document record
|
wizard when sending an email related a previous email (parent_id) or
|
||||||
identified by ``model`` and ``res_id``.
|
a document (model, res_id). This is based on previously computed default
|
||||||
|
values. """
|
||||||
:param str model: model name of the document record this mail is
|
|
||||||
related to.
|
|
||||||
:param int res_id: id of the document record this mail is related to
|
|
||||||
"""
|
|
||||||
doc_name_get = self.pool[model].name_get(cr, uid, [res_id], context=context)
|
|
||||||
record_name = False
|
|
||||||
if doc_name_get:
|
|
||||||
record_name = doc_name_get[0][1]
|
|
||||||
values = {
|
|
||||||
'model': model,
|
|
||||||
'res_id': res_id,
|
|
||||||
'record_name': record_name,
|
|
||||||
}
|
|
||||||
if record_name:
|
|
||||||
values['subject'] = 'Re: %s' % record_name
|
|
||||||
return values
|
|
||||||
|
|
||||||
def get_message_data(self, cr, uid, message_id, context=None):
|
|
||||||
""" Returns a defaults-like dict with initial values for the composition
|
|
||||||
wizard when replying to the given message (e.g. including the quote
|
|
||||||
of the initial message, and the correct recipients).
|
|
||||||
|
|
||||||
:param int message_id: id of the mail.message to which the user
|
|
||||||
is replying.
|
|
||||||
"""
|
|
||||||
if not message_id:
|
|
||||||
return {}
|
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
message_data = self.pool.get('mail.message').browse(cr, uid, message_id, context=context)
|
result, subject = {}, False
|
||||||
|
if values.get('parent_id'):
|
||||||
|
parent = self.pool.get('mail.message').browse(cr, uid, values.get('parent_id'), context=context)
|
||||||
|
result['record_name'] = parent.record_name,
|
||||||
|
subject = tools.ustr(parent.subject or parent.record_name or '')
|
||||||
|
if not values.get('model'):
|
||||||
|
result['model'] = parent.model
|
||||||
|
if not values.get('res_id'):
|
||||||
|
result['res_id'] = parent.res_id
|
||||||
|
partner_ids = values.get('partner_ids', list()) + [partner.id for partner in parent.partner_ids]
|
||||||
|
if context.get('is_private') and parent.author_id: # check message is private then add author also in partner list.
|
||||||
|
partner_ids += [parent.author_id.id]
|
||||||
|
result['partner_ids'] = partner_ids
|
||||||
|
elif values.get('model') and values.get('res_id'):
|
||||||
|
doc_name_get = self.pool[values.get('model')].name_get(cr, uid, [values.get('res_id')], context=context)
|
||||||
|
result['record_name'] = doc_name_get and doc_name_get[0][1] or ''
|
||||||
|
subject = tools.ustr(result['record_name'])
|
||||||
|
|
||||||
# create subject
|
|
||||||
re_prefix = _('Re:')
|
re_prefix = _('Re:')
|
||||||
reply_subject = tools.ustr(message_data.subject or message_data.record_name or '')
|
if subject and not (subject.startswith('Re:') or subject.startswith(re_prefix)):
|
||||||
if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)) and message_data.subject:
|
subject = "%s %s" % (re_prefix, subject)
|
||||||
reply_subject = "%s %s" % (re_prefix, reply_subject)
|
result['subject'] = subject
|
||||||
# get partner_ids from original message
|
|
||||||
partner_ids = [partner.id for partner in message_data.partner_ids] if message_data.partner_ids else []
|
|
||||||
partner_ids += context.get('default_partner_ids', [])
|
|
||||||
if context.get('is_private',False) and message_data.author_id : #check message is private then add author also in partner list.
|
|
||||||
partner_ids += [message_data.author_id.id]
|
|
||||||
# update the result
|
|
||||||
result = {
|
|
||||||
'record_name': message_data.record_name,
|
|
||||||
'model': message_data.model,
|
|
||||||
'res_id': message_data.res_id,
|
|
||||||
'parent_id': message_data.id,
|
|
||||||
'subject': reply_subject,
|
|
||||||
'partner_ids': partner_ids,
|
|
||||||
}
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
|
@ -235,53 +202,42 @@ class mail_compose_message(osv.TransientModel):
|
||||||
email(s), rendering any template patterns on the fly if needed. """
|
email(s), rendering any template patterns on the fly if needed. """
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
|
|
||||||
# clean the context (hint: mass mailing sets some default values that
|
# clean the context (hint: mass mailing sets some default values that
|
||||||
# could be wrongly interpreted by mail_mail)
|
# could be wrongly interpreted by mail_mail)
|
||||||
context.pop('default_email_to', None)
|
context.pop('default_email_to', None)
|
||||||
context.pop('default_partner_ids', None)
|
context.pop('default_partner_ids', None)
|
||||||
|
|
||||||
active_ids = context.get('active_ids')
|
|
||||||
is_log = context.get('mail_compose_log', False)
|
|
||||||
|
|
||||||
for wizard in self.browse(cr, uid, ids, context=context):
|
for wizard in self.browse(cr, uid, ids, context=context):
|
||||||
mass_mail_mode = wizard.composition_mode == 'mass_mail'
|
mass_mode = wizard.composition_mode in ('mass_mail', 'mass_post')
|
||||||
active_model_pool = self.pool[wizard.model if wizard.model else 'mail.thread']
|
active_model_pool = self.pool[wizard.model if wizard.model else 'mail.thread']
|
||||||
if not hasattr(active_model_pool, 'message_post'):
|
if not hasattr(active_model_pool, 'message_post'):
|
||||||
context['thread_model'] = wizard.model
|
context['thread_model'] = wizard.model
|
||||||
active_model_pool = self.pool['mail.thread']
|
active_model_pool = self.pool['mail.thread']
|
||||||
|
|
||||||
# wizard works in batch mode: [res_id] or active_ids or active_domain
|
# wizard works in batch mode: [res_id] or active_ids or active_domain
|
||||||
if mass_mail_mode and wizard.use_active_domain and wizard.model:
|
if mass_mode and wizard.use_active_domain and wizard.model:
|
||||||
res_ids = self.pool[wizard.model].search(cr, uid, eval(wizard.active_domain), context=context)
|
res_ids = self.pool[wizard.model].search(cr, uid, eval(wizard.active_domain), context=context)
|
||||||
elif mass_mail_mode and wizard.model and active_ids:
|
elif mass_mode and wizard.model and context.get('active_ids'):
|
||||||
res_ids = active_ids
|
res_ids = context['active_ids']
|
||||||
else:
|
else:
|
||||||
res_ids = [wizard.res_id]
|
res_ids = [wizard.res_id]
|
||||||
|
|
||||||
all_mail_values = self.get_mail_values(cr, uid, wizard, res_ids, context=context)
|
sliced_res_ids = [res_ids[i:i + self._batch_size] for i in range(0, len(res_ids), self._batch_size)]
|
||||||
|
for res_ids in sliced_res_ids:
|
||||||
for res_id, mail_values in all_mail_values.iteritems():
|
all_mail_values = self.get_mail_values(cr, uid, wizard, res_ids, context=context)
|
||||||
if mass_mail_mode and not wizard.post:
|
for res_id, mail_values in all_mail_values.iteritems():
|
||||||
m2m_attachment_ids = self.pool['mail.thread']._message_preprocess_attachments(
|
if wizard.composition_mode == 'mass_mail':
|
||||||
cr, uid, mail_values.pop('attachments', []),
|
self.pool['mail.mail'].create(cr, uid, mail_values, context=context)
|
||||||
mail_values.pop('attachment_ids', []),
|
else:
|
||||||
'mail.message', 0,
|
subtype = 'mail.mt_comment'
|
||||||
context=context)
|
if context.get('mail_compose_log') or (wizard.composition_mode == 'mass_post' and not wizard.notify): # log a note: subtype is False
|
||||||
mail_values['attachment_ids'] = m2m_attachment_ids
|
|
||||||
if not mail_values.get('reply_to'):
|
|
||||||
mail_values['reply_to'] = mail_values['email_from']
|
|
||||||
self.pool.get('mail.mail').create(cr, uid, mail_values, context=context)
|
|
||||||
else:
|
|
||||||
subtype = 'mail.mt_comment'
|
|
||||||
if is_log: # log a note: subtype is False
|
|
||||||
subtype = False
|
|
||||||
elif mass_mail_mode: # mass mail: is a log pushed to recipients unless specified, author not added
|
|
||||||
if not wizard.notify:
|
|
||||||
subtype = False
|
subtype = False
|
||||||
context = dict(context,
|
if wizard.composition_mode == 'mass_post':
|
||||||
mail_notify_force_send=False, # do not send emails directly but use the queue instead
|
context = dict(context,
|
||||||
mail_create_nosubscribe=True) # add context key to avoid subscribing the author
|
mail_notify_force_send=False, # do not send emails directly but use the queue instead
|
||||||
active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **mail_values)
|
mail_create_nosubscribe=True) # add context key to avoid subscribing the author
|
||||||
|
active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **mail_values)
|
||||||
|
|
||||||
return {'type': 'ir.actions.act_window_close'}
|
return {'type': 'ir.actions.act_window_close'}
|
||||||
|
|
||||||
|
@ -289,6 +245,7 @@ class mail_compose_message(osv.TransientModel):
|
||||||
"""Generate the values that will be used by send_mail to create mail_messages
|
"""Generate the values that will be used by send_mail to create mail_messages
|
||||||
or mail_mails. """
|
or mail_mails. """
|
||||||
results = dict.fromkeys(res_ids, False)
|
results = dict.fromkeys(res_ids, False)
|
||||||
|
rendered_values, default_recipients = {}, {}
|
||||||
mass_mail_mode = wizard.composition_mode == 'mass_mail'
|
mass_mail_mode = wizard.composition_mode == 'mass_mail'
|
||||||
|
|
||||||
# render all template-based value at once
|
# render all template-based value at once
|
||||||
|
@ -303,40 +260,46 @@ class mail_compose_message(osv.TransientModel):
|
||||||
'parent_id': wizard.parent_id and wizard.parent_id.id,
|
'parent_id': wizard.parent_id and wizard.parent_id.id,
|
||||||
'partner_ids': [partner.id for partner in wizard.partner_ids],
|
'partner_ids': [partner.id for partner in wizard.partner_ids],
|
||||||
'attachment_ids': [attach.id for attach in wizard.attachment_ids],
|
'attachment_ids': [attach.id for attach in wizard.attachment_ids],
|
||||||
|
'author_id': wizard.author_id.id,
|
||||||
|
'email_from': wizard.email_from,
|
||||||
|
'record_name': wizard.record_name,
|
||||||
}
|
}
|
||||||
# mass mailing: rendering override wizard static values
|
# mass mailing: rendering override wizard static values
|
||||||
if mass_mail_mode and wizard.model:
|
if mass_mail_mode and wizard.model:
|
||||||
|
# always keep a copy, reset record name (avoid browsing records)
|
||||||
|
mail_values.update(notification=True, model=wizard.model, res_id=res_id, record_name=False)
|
||||||
|
# auto deletion of mail_mail
|
||||||
|
if 'mail_auto_delete' in context:
|
||||||
|
mail_values['auto_delete'] = context.get('mail_auto_delete')
|
||||||
|
# rendered values using template
|
||||||
email_dict = rendered_values[res_id]
|
email_dict = rendered_values[res_id]
|
||||||
mail_values['partner_ids'] += email_dict.pop('partner_ids', [])
|
mail_values['partner_ids'] += email_dict.pop('partner_ids', [])
|
||||||
|
mail_values.update(email_dict)
|
||||||
|
if wizard.same_thread:
|
||||||
|
mail_values.pop('reply_to')
|
||||||
|
elif not mail_values.get('reply_to'):
|
||||||
|
mail_values['reply_to'] = mail_values['email_from']
|
||||||
|
# mail_mail values: body -> body_html, partner_ids -> recipient_ids
|
||||||
|
mail_values['body_html'] = mail_values.get('body', '')
|
||||||
|
mail_values['recipient_ids'] = [(4, id) for id in mail_values.pop('partner_ids', [])]
|
||||||
|
|
||||||
# process attachments: should not be encoded before being processed by message_post / mail_mail create
|
# process attachments: should not be encoded before being processed by message_post / mail_mail create
|
||||||
attachments = []
|
mail_values['attachments'] = [(name, base64.b64decode(enc_cont)) for name, enc_cont in email_dict.pop('attachments', list())]
|
||||||
if email_dict.get('attachments'):
|
|
||||||
for name, enc_cont in email_dict.pop('attachments'):
|
|
||||||
attachments.append((name, base64.b64decode(enc_cont)))
|
|
||||||
mail_values['attachments'] = attachments
|
|
||||||
attachment_ids = []
|
attachment_ids = []
|
||||||
for attach_id in mail_values.pop('attachment_ids'):
|
for attach_id in mail_values.pop('attachment_ids'):
|
||||||
new_attach_id = self.pool.get('ir.attachment').copy(cr, uid, attach_id, {'res_model': self._name, 'res_id': wizard.id}, context=context)
|
new_attach_id = self.pool.get('ir.attachment').copy(cr, uid, attach_id, {'res_model': self._name, 'res_id': wizard.id}, context=context)
|
||||||
attachment_ids.append(new_attach_id)
|
attachment_ids.append(new_attach_id)
|
||||||
mail_values['attachment_ids'] = attachment_ids
|
mail_values['attachment_ids'] = self.pool['mail.thread']._message_preprocess_attachments(
|
||||||
# email_from: mass mailing only can specify another email_from
|
cr, uid, mail_values.pop('attachments', []),
|
||||||
if email_dict.get('email_from'):
|
attachment_ids, 'mail.message', 0, context=context)
|
||||||
mail_values['email_from'] = email_dict.pop('email_from')
|
|
||||||
# replies redirection: mass mailing only
|
|
||||||
if wizard.same_thread and wizard.post:
|
|
||||||
email_dict.pop('reply_to', None)
|
|
||||||
else:
|
|
||||||
mail_values['reply_to'] = email_dict.pop('reply_to', None)
|
|
||||||
mail_values.update(email_dict)
|
|
||||||
# mass mailing without post: mail_mail values
|
|
||||||
if mass_mail_mode and not wizard.post:
|
|
||||||
if 'mail_auto_delete' in context:
|
|
||||||
mail_values['auto_delete'] = context.get('mail_auto_delete')
|
|
||||||
mail_values['body_html'] = mail_values.get('body', '')
|
|
||||||
mail_values['recipient_ids'] = [(4, id) for id in mail_values.pop('partner_ids', [])]
|
|
||||||
results[res_id] = mail_values
|
results[res_id] = mail_values
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
#------------------------------------------------------
|
||||||
|
# Template rendering
|
||||||
|
#------------------------------------------------------
|
||||||
|
|
||||||
def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
|
def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
|
||||||
"""Generate template-based values of wizard, for the document records given
|
"""Generate template-based values of wizard, for the document records given
|
||||||
by res_ids. This method is meant to be inherited by email_template that
|
by res_ids. This method is meant to be inherited by email_template that
|
||||||
|
@ -346,6 +309,10 @@ class mail_compose_message(osv.TransientModel):
|
||||||
once, and render it multiple times. This is useful for mass mailing where
|
once, and render it multiple times. This is useful for mass mailing where
|
||||||
template rendering represent a significant part of the process.
|
template rendering represent a significant part of the process.
|
||||||
|
|
||||||
|
Default recipients are also computed, based on mail_thread method
|
||||||
|
message_get_default_recipients. This allows to ensure a mass mailing has
|
||||||
|
always some recipients specified.
|
||||||
|
|
||||||
:param browse wizard: current mail.compose.message browse record
|
:param browse wizard: current mail.compose.message browse record
|
||||||
:param list res_ids: list of record ids
|
:param list res_ids: list of record ids
|
||||||
|
|
||||||
|
@ -357,6 +324,9 @@ class mail_compose_message(osv.TransientModel):
|
||||||
emails_from = self.render_template_batch(cr, uid, wizard.email_from, wizard.model, res_ids, context=context)
|
emails_from = self.render_template_batch(cr, uid, wizard.email_from, wizard.model, res_ids, context=context)
|
||||||
replies_to = self.render_template_batch(cr, uid, wizard.reply_to, wizard.model, res_ids, context=context)
|
replies_to = self.render_template_batch(cr, uid, wizard.reply_to, wizard.model, res_ids, context=context)
|
||||||
|
|
||||||
|
ctx = dict(context, thread_model=wizard.model)
|
||||||
|
default_recipients = self.pool['mail.thread'].message_get_default_recipients(cr, uid, res_ids, context=ctx)
|
||||||
|
|
||||||
results = dict.fromkeys(res_ids, False)
|
results = dict.fromkeys(res_ids, False)
|
||||||
for res_id in res_ids:
|
for res_id in res_ids:
|
||||||
results[res_id] = {
|
results[res_id] = {
|
||||||
|
@ -365,6 +335,7 @@ class mail_compose_message(osv.TransientModel):
|
||||||
'email_from': emails_from[res_id],
|
'email_from': emails_from[res_id],
|
||||||
'reply_to': replies_to[res_id],
|
'reply_to': replies_to[res_id],
|
||||||
}
|
}
|
||||||
|
results[res_id].update(default_recipients.get(res_id, dict()))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def render_template_batch(self, cr, uid, template, model, res_ids, context=None, post_process=False):
|
def render_template_batch(self, cr, uid, template, model, res_ids, context=None, post_process=False):
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<field name="composition_mode" invisible="1"/>
|
<field name="composition_mode" invisible="1"/>
|
||||||
<field name="model" invisible="1"/>
|
<field name="model" invisible="1"/>
|
||||||
<field name="res_id" invisible="1"/>
|
<field name="res_id" invisible="1"/>
|
||||||
|
<field name="is_log" invisible="1"/>
|
||||||
<field name="parent_id" invisible="1"/>
|
<field name="parent_id" invisible="1"/>
|
||||||
<field name="mail_server_id" invisible="1"/>
|
<field name="mail_server_id" invisible="1"/>
|
||||||
<!-- Various warnings -->
|
<!-- Various warnings -->
|
||||||
|
@ -28,29 +29,27 @@
|
||||||
<field name="email_from"
|
<field name="email_from"
|
||||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||||
<field name="subject" placeholder="Subject..." required="True"/>
|
<field name="subject" placeholder="Subject..." required="True"/>
|
||||||
<!-- classic message composer -->
|
<!-- recipients -->
|
||||||
<label for="partner_ids" string="Recipients"
|
<label for="partner_ids" string="Recipients" attrs="{'invisible': [('is_log', '=', True)]}" groups="base.group_user"/>
|
||||||
attrs="{'invisible':[('composition_mode', '=', 'mass_mail')]}"/>
|
<div groups="base.group_user" attrs="{'invisible': [('is_log', '=', True)]}">
|
||||||
<div groups="base.group_user"
|
<span attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}">
|
||||||
attrs="{'invisible':[('composition_mode', '=', 'mass_mail')]}">
|
<strong>Email mass mailing</strong> on
|
||||||
<span attrs="{'invisible':[('model', '=', False)]}">
|
<span attrs="{'invisible': [('use_active_domain', '=', True)]}">the selected records</span>
|
||||||
Followers of
|
<span attrs="{'invisible': [('use_active_domain', '=', False)]}">the current search filter</span>.
|
||||||
<field name="record_name" readonly="1" class="oe_inline oe_compose_recipients"/>
|
|
||||||
and
|
|
||||||
</span>
|
</span>
|
||||||
|
<span attrs="{'invisible':[('composition_mode', '!=', 'comment')]}">Followers of the document and</span>
|
||||||
<field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to notify..."
|
<field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to notify..."
|
||||||
context="{'force_email':True, 'show_email':True}"/>
|
context="{'force_email':True, 'show_email':True}"
|
||||||
|
attrs="{'invisible': [('composition_mode', '!=', 'comment')]}"/>
|
||||||
</div>
|
</div>
|
||||||
<!-- mass post / mass mailing -->
|
<!-- mass post -->
|
||||||
<field name="post"
|
|
||||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
|
||||||
<field name="notify"
|
<field name="notify"
|
||||||
attrs="{'invisible':['|', ('post', '!=', True), ('composition_mode', '!=', 'mass_mail')]}"/>
|
attrs="{'invisible':['|', ('composition_mode', '!=', 'mass_post')]}"/>
|
||||||
<field name="same_thread"
|
<!-- mass mailing -->
|
||||||
attrs="{'invisible':['|', ('composition_mode', '!=', 'mass_mail'), ('post', '=', False)]}"/>
|
<field name="same_thread" attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||||
<field name="reply_to" placeholder="Email address te redirect replies..."
|
<field name="reply_to" placeholder="Email address to redirect replies..."
|
||||||
attrs="{'invisible':['|', '&', ('same_thread', '=', True), ('post', '=', True), ('composition_mode', '!=', 'mass_mail')],
|
attrs="{'invisible':['|', ('same_thread', '=', True), ('composition_mode', '!=', 'mass_mail')],
|
||||||
'required':['&', '|', ('post', '=', False), ('same_thread', '=', False), ('composition_mode', '=', 'mass_mail')]}"/>
|
'required':[('same_thread', '!=', True), ('composition_mode', '=', 'mass_mail')]}"/>
|
||||||
</group>
|
</group>
|
||||||
<field name="body"/>
|
<field name="body"/>
|
||||||
<field name="attachment_ids" widget="many2many_binary" string="Attach a file"/>
|
<field name="attachment_ids" widget="many2many_binary" string="Attach a file"/>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# OpenERP, Open Source Management Solution
|
# OpenERP, Open Source Management Solution
|
||||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
# Copyright (C) 2004-TODAY OpenERP SA (http://www.openerp.com)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
# GNU Affero General Public License for more details.
|
# GNU Affero General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Marketing',
|
'name': 'Marketing',
|
||||||
'version': '1.1',
|
'version': '1.1',
|
||||||
'depends': ['base', 'base_setup', 'crm'],
|
'depends': ['base', 'base_setup'],
|
||||||
'author': 'OpenERP SA',
|
'author': 'OpenERP SA',
|
||||||
'category': 'Hidden/Dependency',
|
'category': 'Hidden/Dependency',
|
||||||
'description': """
|
'description': """
|
||||||
|
@ -35,7 +15,6 @@ Contains the installer for marketing-related modules.
|
||||||
'website': 'http://www.openerp.com',
|
'website': 'http://www.openerp.com',
|
||||||
'data': [
|
'data': [
|
||||||
'security/marketing_security.xml',
|
'security/marketing_security.xml',
|
||||||
'security/ir.model.access.csv',
|
|
||||||
'marketing_view.xml',
|
'marketing_view.xml',
|
||||||
'res_config_view.xml',
|
'res_config_view.xml',
|
||||||
],
|
],
|
||||||
|
@ -44,4 +23,3 @@ Contains the installer for marketing-related modules.
|
||||||
'auto_install': False,
|
'auto_install': False,
|
||||||
'images': ['images/config_marketing.jpeg'],
|
'images': ['images/config_marketing.jpeg'],
|
||||||
}
|
}
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -3,39 +3,12 @@
|
||||||
<data>
|
<data>
|
||||||
|
|
||||||
<!-- Top menu item -->
|
<!-- Top menu item -->
|
||||||
<menuitem name="Marketing"
|
<menuitem name="Marketing" id="base.marketing_menu" sequence="85"
|
||||||
id="base.marketing_menu"
|
groups="base.group_user"/>
|
||||||
groups="base.group_user"
|
|
||||||
sequence="85"/>
|
|
||||||
|
|
||||||
<record id="view_crm_lead_form" model="ir.ui.view">
|
<!-- Reporting for Marketing -->
|
||||||
<field name="name">crm.lead.inherit.form</field>
|
<menuitem name="Marketing" id="base.marketing_reporting_menu" sequence="10"
|
||||||
<field name="model">crm.lead</field>
|
parent="base.menu_reporting" />
|
||||||
<field name="inherit_id" ref="crm.crm_case_form_view_leads"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//group[@name='categorization']" position="attributes">
|
|
||||||
<attribute name="string">Marketing</attribute>
|
|
||||||
<attribute name="groups"></attribute>
|
|
||||||
</xpath>
|
|
||||||
<xpath expr="//field[@name='company_id']" position="after">
|
|
||||||
<field name="type_id"/>
|
|
||||||
<field name="channel_id" widget="selection"/>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="view_crm_opportunity_form" model="ir.ui.view">
|
|
||||||
<field name="name">crm.lead.inherit.form</field>
|
|
||||||
<field name="model">crm.lead</field>
|
|
||||||
<field name="inherit_id" ref="crm.crm_case_form_view_oppor"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<xpath expr="//group[@name='mailings']" position="before">
|
|
||||||
<group string="Marketing">
|
|
||||||
<field name="type_id" />
|
|
||||||
<field name="channel_id" widget="selection"/>
|
|
||||||
</group>
|
|
||||||
</xpath>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|
|
@ -1,40 +1,19 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Business Applications
|
|
||||||
# Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
|
|
||||||
#
|
|
||||||
# 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 openerp.osv import fields, osv
|
from openerp.osv import fields, osv
|
||||||
|
|
||||||
class marketing_config_settings(osv.osv_memory):
|
|
||||||
|
class marketing_config_settings(osv.TransientModel):
|
||||||
_name = 'marketing.config.settings'
|
_name = 'marketing.config.settings'
|
||||||
_inherit = 'res.config.settings'
|
_inherit = 'res.config.settings'
|
||||||
_columns = {
|
_columns = {
|
||||||
'module_marketing_campaign': fields.boolean('Marketing campaigns',
|
'module_mass_mailing': fields.boolean(
|
||||||
|
'Mass Mailing',
|
||||||
|
help='Provide a way to perform mass mailings.\n'
|
||||||
|
'-This installs the module mass_mailing.'),
|
||||||
|
'module_marketing_campaign': fields.boolean(
|
||||||
|
'Marketing campaigns',
|
||||||
help='Provides leads automation through marketing campaigns. '
|
help='Provides leads automation through marketing campaigns. '
|
||||||
'Campaigns can in fact be defined on any resource, not just CRM leads.\n'
|
'Campaigns can in fact be defined on any resource, not just CRM leads.\n'
|
||||||
'-This installs the module marketing_campaign.'),
|
'-This installs the module marketing_campaign.'),
|
||||||
'module_marketing_campaign_crm_demo': fields.boolean('Demo data for marketing campaigns',
|
|
||||||
help='Installs demo data like leads, campaigns and segments for Marketing Campaigns.\n'
|
|
||||||
'-This installs the module marketing_campaign_crm_demo.'),
|
|
||||||
'module_crm_profiling': fields.boolean('Track customer profile to focus your campaigns',
|
|
||||||
help='Allows users to perform segmentation within partners.\n'
|
|
||||||
'-This installs the module crm_profiling.'),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -11,24 +11,25 @@
|
||||||
<button string="Apply" type="object" name="execute" class="oe_highlight"/>
|
<button string="Apply" type="object" name="execute" class="oe_highlight"/>
|
||||||
or
|
or
|
||||||
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
|
||||||
|
|
||||||
</header>
|
</header>
|
||||||
<separator string="Campaigns"/>
|
<separator string="Mass Mailing"/>
|
||||||
<group>
|
<group>
|
||||||
<label for="id" string="Campaigns Settings"/>
|
<label for="id" string="Settings"/>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div name="module_mass_mailing">
|
||||||
|
<field name="module_mass_mailing" class="oe_inline"/>
|
||||||
|
<label for="module_mass_mailing"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
<separator string="Marketing Campaigns"/>
|
||||||
|
<group>
|
||||||
|
<label for="id" string="Settings"/>
|
||||||
|
<div>
|
||||||
|
<div name="module_marketing_campaign">
|
||||||
<field name="module_marketing_campaign" class="oe_inline"/>
|
<field name="module_marketing_campaign" class="oe_inline"/>
|
||||||
<label for="module_marketing_campaign"/>
|
<label for="module_marketing_campaign"/>
|
||||||
</div>
|
</div>
|
||||||
<div attrs="{'invisible':[('module_marketing_campaign','=',False)]}">
|
|
||||||
<field name="module_marketing_campaign_crm_demo" class="oe_inline"/>
|
|
||||||
<label for="module_marketing_campaign_crm_demo"/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<field name="module_crm_profiling" class="oe_inline"/>
|
|
||||||
<label for="module_crm_profiling"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</group>
|
</group>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
|
|
|
@ -1,8 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# OpenERP, Open Source Management Solution
|
# OpenERP, Open Source Management Solution
|
||||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
# Copyright (C) 2004-TODAY OpenERP SA (http://www.openerp.com)
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -15,23 +15,8 @@
|
||||||
# GNU Affero General Public License for more details.
|
# GNU Affero General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
import datetime
|
import models
|
||||||
import time
|
|
||||||
|
|
||||||
from openerp.report import report_sxw
|
|
||||||
|
|
||||||
class expense(report_sxw.rml_parse):
|
|
||||||
|
|
||||||
def __init__(self, cr, uid, name, context):
|
|
||||||
super(expense, self).__init__(cr, uid, name, context=context)
|
|
||||||
self.localcontext.update({'time': time, })
|
|
||||||
|
|
||||||
report_sxw.report_sxw('report.hr.expense', 'hr.expense.expense', 'addons/hr_expense/report/expense.rml',parser=expense)
|
|
||||||
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Marketing in CRM',
|
||||||
|
'version': '1.0',
|
||||||
|
'depends': ['marketing', 'crm'],
|
||||||
|
'author': 'OpenERP SA',
|
||||||
|
'category': 'Hidden/Dependency',
|
||||||
|
'description': """
|
||||||
|
Bridge module between marketing and CRM
|
||||||
|
""",
|
||||||
|
'website': 'http://www.openerp.com',
|
||||||
|
'data': [
|
||||||
|
'views/crm.xml',
|
||||||
|
'views/res_config.xml',
|
||||||
|
],
|
||||||
|
'demo': [],
|
||||||
|
'installable': True,
|
||||||
|
'auto_install': True,
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import res_config
|
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from openerp.osv import fields, osv
|
||||||
|
|
||||||
|
|
||||||
|
class CrmMarketingConfig(osv.TransientModel):
|
||||||
|
_name = 'marketing.config.settings'
|
||||||
|
_inherit = 'marketing.config.settings'
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'module_marketing_campaign_crm_demo': fields.boolean(
|
||||||
|
'Demo data for marketing campaigns',
|
||||||
|
help='Installs demo data like leads, campaigns and segments for Marketing Campaigns.\n'
|
||||||
|
'-This installs the module marketing_campaign_crm_demo.'),
|
||||||
|
'module_crm_profiling': fields.boolean(
|
||||||
|
'Track customer profile to focus your campaigns',
|
||||||
|
help='Allows users to perform segmentation within partners.\n'
|
||||||
|
'-This installs the module crm_profiling.'),
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record id="view_crm_lead_form" model="ir.ui.view">
|
||||||
|
<field name="name">crm.lead.inherit.form</field>
|
||||||
|
<field name="model">crm.lead</field>
|
||||||
|
<field name="inherit_id" ref="crm.crm_case_form_view_leads"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//group[@name='categorization']" position="attributes">
|
||||||
|
<attribute name="string">Marketing</attribute>
|
||||||
|
<attribute name="groups"></attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='company_id']" position="after">
|
||||||
|
<field name="type_id"/>
|
||||||
|
<field name="channel_id" widget="selection"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_crm_opportunity_form" model="ir.ui.view">
|
||||||
|
<field name="name">crm.lead.inherit.form</field>
|
||||||
|
<field name="model">crm.lead</field>
|
||||||
|
<field name="inherit_id" ref="crm.crm_case_form_view_oppor"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//group[@name='mailings']" position="before">
|
||||||
|
<group string="Marketing">
|
||||||
|
<field name="type_id" />
|
||||||
|
<field name="channel_id" widget="selection"/>
|
||||||
|
</group>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record id="view_marketing_configuration" model="ir.ui.view">
|
||||||
|
<field name="name">marketing.config.settings.crm</field>
|
||||||
|
<field name="model">marketing.config.settings</field>
|
||||||
|
<field name="inherit_id" ref="marketing.view_marketing_configuration"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//div[@name='module_marketing_campaign']" position="after">
|
||||||
|
<div attrs="{'invisible':[('module_marketing_campaign','=',False)]}">
|
||||||
|
<field name="module_marketing_campaign_crm_demo" class="oe_inline"/>
|
||||||
|
<label for="module_marketing_campaign_crm_demo"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<field name="module_crm_profiling" class="oe_inline"/>
|
||||||
|
<label for="module_crm_profiling"/>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -1,26 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>)
|
|
||||||
#
|
|
||||||
# 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 mass_mailing
|
import models
|
||||||
import mail_mail
|
|
||||||
import mail_thread
|
|
||||||
import wizard
|
import wizard
|
||||||
import controllers
|
import controllers
|
||||||
|
|
|
@ -21,26 +21,33 @@
|
||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Mass Mailing Campaigns',
|
'name': 'Mass Mailing Campaigns',
|
||||||
|
'summary': 'Design, send and track emails',
|
||||||
'description': """
|
'description': """
|
||||||
Easily send mass mailing to your leads, opportunities or customers. Track
|
Easily send mass mailing to your leads, opportunities or customers. Track
|
||||||
marketing campaigns performance to improve conversion rates. Design
|
marketing campaigns performance to improve conversion rates. Design
|
||||||
professional emails and reuse templates in a few clicks.
|
professional emails and reuse templates in a few clicks.
|
||||||
""",
|
""",
|
||||||
'version': '1.0',
|
'version': '2.0',
|
||||||
'author': 'OpenERP',
|
'author': 'OpenERP',
|
||||||
'website': 'http://www.openerp.com',
|
'website': 'http://www.openerp.com',
|
||||||
'category': 'Marketing',
|
'category': 'Marketing',
|
||||||
'depends': [
|
'depends': [
|
||||||
'mail',
|
'mail',
|
||||||
'email_template',
|
'email_template',
|
||||||
|
'marketing',
|
||||||
'web_kanban_gauge',
|
'web_kanban_gauge',
|
||||||
'web_kanban_sparkline',
|
'web_kanban_sparkline',
|
||||||
|
'website_mail',
|
||||||
],
|
],
|
||||||
'data': [
|
'data': [
|
||||||
'mail_data.xml',
|
'data/mail_data.xml',
|
||||||
|
'data/mass_mailing_data.xml',
|
||||||
'wizard/mail_compose_message_view.xml',
|
'wizard/mail_compose_message_view.xml',
|
||||||
'wizard/mail_mass_mailing_create_segment.xml',
|
'wizard/test_mailing.xml',
|
||||||
'mass_mailing_view.xml',
|
'views/mass_mailing.xml',
|
||||||
|
'views/res_config.xml',
|
||||||
|
'views/res_partner.xml',
|
||||||
|
'views/email_template.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
],
|
],
|
||||||
'js': [
|
'js': [
|
||||||
|
@ -48,10 +55,11 @@ professional emails and reuse templates in a few clicks.
|
||||||
],
|
],
|
||||||
'qweb': [],
|
'qweb': [],
|
||||||
'css': [
|
'css': [
|
||||||
'static/src/css/mass_mailing.css'
|
'static/src/css/mass_mailing.css',
|
||||||
|
'static/src/css/email_template.css'
|
||||||
],
|
],
|
||||||
'demo': [
|
'demo': [
|
||||||
'mass_mailing_demo.xml',
|
'data/mass_mailing_demo.xml',
|
||||||
],
|
],
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'auto_install': False,
|
'auto_install': False,
|
||||||
|
|
|
@ -1,11 +1,42 @@
|
||||||
|
|
||||||
|
import werkzeug
|
||||||
|
|
||||||
from openerp import http, SUPERUSER_ID
|
from openerp import http, SUPERUSER_ID
|
||||||
from openerp.http import request
|
from openerp.http import request
|
||||||
|
|
||||||
|
|
||||||
class MassMailController(http.Controller):
|
class MassMailController(http.Controller):
|
||||||
|
|
||||||
@http.route('/mail/track/<int:mail_id>/blank.gif', type='http', auth='none')
|
@http.route('/mail/track/<int:mail_id>/blank.gif', type='http', auth='none')
|
||||||
def track_mail_open(self, mail_id):
|
def track_mail_open(self, mail_id, **post):
|
||||||
""" Email tracking. """
|
""" Email tracking. """
|
||||||
mail_mail_stats = request.registry.get('mail.mail.statistics')
|
mail_mail_stats = request.registry.get('mail.mail.statistics')
|
||||||
mail_mail_stats.set_opened(request.cr, SUPERUSER_ID, mail_mail_ids=[mail_id])
|
mail_mail_stats.set_opened(request.cr, SUPERUSER_ID, mail_mail_ids=[mail_id])
|
||||||
return "data:image/gif;base64,R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
|
response = werkzeug.wrappers.Response()
|
||||||
|
response.mimetype = 'image/gif'
|
||||||
|
response.set_data('R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='.decode('base64'))
|
||||||
|
return response
|
||||||
|
|
||||||
|
@http.route(['/mail/mailing/<int:mailing_id>/unsubscribe'], type='http', auth='none')
|
||||||
|
def mailing(self, mailing_id, email=None, res_id=None, **post):
|
||||||
|
cr, uid, context = request.cr, request.uid, request.context
|
||||||
|
MassMailing = request.registry['mail.mass_mailing']
|
||||||
|
mailing_ids = MassMailing.exists(cr, SUPERUSER_ID, [mailing_id], context=context)
|
||||||
|
if not mailing_ids:
|
||||||
|
return 'KO'
|
||||||
|
mailing = MassMailing.browse(cr, SUPERUSER_ID, mailing_ids[0], context=context)
|
||||||
|
if mailing.mailing_model == 'mail.mass_mailing.contact':
|
||||||
|
list_ids = [l.id for l in mailing.contact_list_ids]
|
||||||
|
record_ids = request.registry[mailing.mailing_model].search(cr, SUPERUSER_ID, [('list_id', 'in', list_ids), ('id', '=', res_id), ('email', 'ilike', email)], context=context)
|
||||||
|
request.registry[mailing.mailing_model].write(cr, SUPERUSER_ID, record_ids, {'opt_out': True}, context=context)
|
||||||
|
else:
|
||||||
|
email_fname = None
|
||||||
|
if 'email_from' in request.registry[mailing.mailing_model]._all_columns:
|
||||||
|
email_fname = 'email_from'
|
||||||
|
elif 'email' in request.registry[mailing.mailing_model]._all_columns:
|
||||||
|
email_fname = 'email'
|
||||||
|
if email_fname:
|
||||||
|
record_ids = request.registry[mailing.mailing_model].search(cr, SUPERUSER_ID, [('id', '=', res_id), (email_fname, 'ilike', email)], context=context)
|
||||||
|
if 'opt_out' in request.registry[mailing.mailing_model]._all_columns:
|
||||||
|
request.registry[mailing.mailing_model].write(cr, SUPERUSER_ID, record_ids, {'opt_out': True}, context=context)
|
||||||
|
return 'OK'
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data noupdate="1">
|
||||||
|
|
||||||
|
<!-- After installation of the module, open the related menu -->
|
||||||
|
<record id="action_client_marketing_menu" model="ir.actions.client">
|
||||||
|
<field name="name">Open Marketing Menu</field>
|
||||||
|
<field name="tag">reload</field>
|
||||||
|
<field name="params" eval="{'menu_id': ref('base.marketing_menu')}"/>
|
||||||
|
</record>
|
||||||
|
<record id="base.open_menu" model="ir.actions.todo">
|
||||||
|
<field name="action_id" ref="action_client_marketing_menu"/>
|
||||||
|
<field name="state">open</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Group to manage campaigns -->
|
||||||
|
<record id="group_mass_mailing_campaign" model="res.groups">
|
||||||
|
<field name="name">Manage Mass Mailing Campaigns</field>
|
||||||
|
<field name="category_id" ref="base.module_category_hidden"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Default stages of mass mailing campaigns -->
|
||||||
|
<record id="campaign_stage_1" model="mail.mass_mailing.stage">
|
||||||
|
<field name="name">Schedule</field>
|
||||||
|
<field name="sequence">10</field>
|
||||||
|
</record>
|
||||||
|
<record id="campaign_stage_2" model="mail.mass_mailing.stage">
|
||||||
|
<field name="name">Design</field>
|
||||||
|
<field name="sequence">20</field>
|
||||||
|
</record>
|
||||||
|
<record id="campaign_stage_3" model="mail.mass_mailing.stage">
|
||||||
|
<field name="name">Sent</field>
|
||||||
|
<field name="sequence">30</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -0,0 +1,146 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<openerp>
|
||||||
|
<data noupdate="1">
|
||||||
|
|
||||||
|
<record id="mass_mail_attach_1" model="ir.attachment">
|
||||||
|
<field name="datas">bWlncmF0aW9uIHRlc3Q=</field>
|
||||||
|
<field name="datas_fname">SampleDoc.doc</field>
|
||||||
|
<field name="name">SampleDoc.doc</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Create mailing lists -->
|
||||||
|
<record id="mass_mail_list_1" model="mail.mass_mailing.list">
|
||||||
|
<field name="name">Imported Contacts</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Create Contacts -->
|
||||||
|
<record id="mass_mail_contact_1" model="mail.mass_mailing.contact">
|
||||||
|
<field name="name">Aristide Antario</field>
|
||||||
|
<field name="email">aa@example.com</field>
|
||||||
|
<field name="list_id" ref="mass_mailing.mass_mail_list_1"/>
|
||||||
|
</record>
|
||||||
|
<record id="mass_mail_contact_2" model="mail.mass_mailing.contact">
|
||||||
|
<field name="name">Beverly Bridge</field>
|
||||||
|
<field name="email">bb@example.com</field>
|
||||||
|
<field name="list_id" ref="mass_mailing.mass_mail_list_1"/>
|
||||||
|
</record>
|
||||||
|
<record id="mass_mail_contact_3" model="mail.mass_mailing.contact">
|
||||||
|
<field name="name">Carol Cartridge</field>
|
||||||
|
<field name="email">cc@example.com</field>
|
||||||
|
<field name="list_id" ref="mass_mailing.mass_mail_list_1"/>
|
||||||
|
<field name="opt_out" eval="True"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Create campaign and mailings -->
|
||||||
|
<record id="mass_mail_category_1" model="mail.mass_mailing.category">
|
||||||
|
<field name="name">Marketing</field>
|
||||||
|
</record>
|
||||||
|
<record id="mass_mail_campaign_1" model="mail.mass_mailing.campaign">
|
||||||
|
<field name="name">Newsletter</field>
|
||||||
|
<field name="stage_id" ref="mass_mailing.campaign_stage_1"/>
|
||||||
|
<field name="user_id" eval="ref('base.user_root')"/>
|
||||||
|
<field name="category_ids" eval="[(6,0,[ref('mass_mailing.mass_mail_category_1')])]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="mass_mail_1" model="mail.mass_mailing">
|
||||||
|
<field name="name">First Newsletter</field>
|
||||||
|
<field name="state">done</field>
|
||||||
|
<field name="sent_date" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
|
<field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
||||||
|
<field name="mailing_model">res.partner</field>
|
||||||
|
<field name="mailing_domain">[('customer', '=', True)]</field>
|
||||||
|
<field name="reply_to_mode">email</field>
|
||||||
|
<field name="reply_to"><![CDATA[Info <info@yourcompany.example.com>]]></field>
|
||||||
|
<field name="body_html"><![CDATA[<div data-snippet-id="big-picture" style="padding:0px; margin:0px">
|
||||||
|
<table cellpadding="0" cellspacing="0" style="margin:10px 0px 0px;vertical-align:top;padding:0px;font-family:arial;font-size:12px;color:rgb(51,51,51)">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width:600px" valign="top">
|
||||||
|
<h2 style="text-align: center; padding:0px 5px">A Punchy Headline</h2>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="width:600px" valign="top"><img src="/website/static/src/img/big_picture.png" style="display:block;border:none;min-height:250px;margin:0 auto;" width="500"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="width:600px" valign="top">
|
||||||
|
<p style="text-align: center; overflow:hidden"></p>
|
||||||
|
|
||||||
|
<h3 style="text-align: center; padding:0px 5px">A Small Subtitle for ${object.name}</h3>
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<p style="text-align: center; overflow:hidden">Choose a vibrant image and write an inspiring paragraph about it. It does not have to be long, but it should reinforce your image.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div data-snippet-id="three-columns" style="padding:0px; margin:0px">
|
||||||
|
<table cellpadding="0" cellspacing="0" style="margin:10px 0px 0px;vertical-align:top;padding:0px;font-family:arial;font-size:12px;color:rgb(51,51,51)">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width:300px" valign="top"><img src="/website/static/src/img/desert_thumb.jpg" style="display:block;border:none;min-height:50px" width="275"></td>
|
||||||
|
<td style="width:300px" valign="top"><img src="/website/static/src/img/deers_thumb.jpg" style="display:block;border:none;min-height:50px" width="275"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="width:300px" valign="top">
|
||||||
|
<h3 style="text-align: center; padding:0px 5px">Feature One</h3>
|
||||||
|
|
||||||
|
<p style="overflow:hidden">Choose a vibrant image and write an inspiring paragraph about it. It does not have to be long, but it should reinforce your image.</p>
|
||||||
|
</td>
|
||||||
|
<td style="width:300px" valign="top">
|
||||||
|
<h3 style="text-align: center; padding:0px 5px">Feature Two</h3>
|
||||||
|
|
||||||
|
<p style="overflow:hidden">Choose a vibrant image and write an inspiring paragraph about it. It does not have to be long, but it should reinforce your image.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>]]></field>
|
||||||
|
<field name="attachment_ids" eval="[(4, ref('mass_mail_attach_1'))]"/>
|
||||||
|
</record>
|
||||||
|
<record id="mass_mail_2" model="mail.mass_mailing">
|
||||||
|
<field name="name">Second Newsletter</field>
|
||||||
|
<field name="state">test</field>
|
||||||
|
<field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
||||||
|
<field name="mailing_model">res.partner</field>
|
||||||
|
<field name="mailing_domain">[('customer', '=', True)]</field>
|
||||||
|
<field name="reply_to_mode">email</field>
|
||||||
|
<field name="reply_to"><![CDATA[Info <info@yourcompany.example.com>]]></field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="mass_mail_email_1" model="mail.mail.statistics">
|
||||||
|
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||||
|
<field name="message_id">1111000@OpenERP.com</field>
|
||||||
|
<field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
|
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
|
<field name="replied" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
|
</record>
|
||||||
|
<record id="mass_mail_email_2" model="mail.mail.statistics">
|
||||||
|
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||||
|
<field name="message_id">1111001@OpenERP.com</field>
|
||||||
|
<field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
|
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
|
<field name="replied" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
|
</record>
|
||||||
|
<record id="mass_mail_email_3" model="mail.mail.statistics">
|
||||||
|
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||||
|
<field name="message_id">1111002@OpenERP.com</field>
|
||||||
|
<field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
|
<field name="opened" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
|
</record>
|
||||||
|
<record id="mass_mail_email_4" model="mail.mail.statistics">
|
||||||
|
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||||
|
<field name="message_id">1111003@OpenERP.com</field>
|
||||||
|
<field name="exception" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
|
</record>
|
||||||
|
<record id="mass_mail_email_5" model="mail.mail.statistics">
|
||||||
|
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||||
|
<field name="message_id">1111004@OpenERP.com</field>
|
||||||
|
<field name="sent" eval="(DateTime.today() - relativedelta(days=5)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
|
<field name="bounced" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -1,369 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>)
|
|
||||||
#
|
|
||||||
# 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 datetime import datetime
|
|
||||||
from dateutil import relativedelta
|
|
||||||
|
|
||||||
from openerp import tools
|
|
||||||
from openerp.tools.translate import _
|
|
||||||
from openerp.osv import osv, fields
|
|
||||||
|
|
||||||
|
|
||||||
class MassMailingCampaign(osv.Model):
|
|
||||||
"""Model of mass mailing campaigns.
|
|
||||||
"""
|
|
||||||
_name = "mail.mass_mailing.campaign"
|
|
||||||
_description = 'Mass Mailing Campaign'
|
|
||||||
# number of embedded mailings in kanban view
|
|
||||||
_kanban_mailing_nbr = 4
|
|
||||||
|
|
||||||
def _get_statistics(self, cr, uid, ids, name, arg, context=None):
|
|
||||||
""" Compute statistics of the mass mailing campaign """
|
|
||||||
results = dict.fromkeys(ids, False)
|
|
||||||
for campaign in self.browse(cr, uid, ids, context=context):
|
|
||||||
results[campaign.id] = {
|
|
||||||
'sent': len(campaign.statistics_ids),
|
|
||||||
# delivered: shouldn't be: all mails - (failed + bounced) ?
|
|
||||||
'delivered': len([stat for stat in campaign.statistics_ids if not stat.bounced]), # stat.state == 'sent' and
|
|
||||||
'opened': len([stat for stat in campaign.statistics_ids if stat.opened]),
|
|
||||||
'replied': len([stat for stat in campaign.statistics_ids if stat.replied]),
|
|
||||||
'bounced': len([stat for stat in campaign.statistics_ids if stat.bounced]),
|
|
||||||
}
|
|
||||||
return results
|
|
||||||
|
|
||||||
def _get_mass_mailing_kanban_ids(self, cr, uid, ids, name, arg, context=None):
|
|
||||||
""" Gather data about mass mailings to display them in kanban view as
|
|
||||||
nested kanban views is not possible currently. """
|
|
||||||
results = dict.fromkeys(ids, '')
|
|
||||||
for campaign in self.browse(cr, uid, ids, context=context):
|
|
||||||
mass_mailing_results = []
|
|
||||||
for mass_mailing in campaign.mass_mailing_ids[:self._kanban_mailing_nbr]:
|
|
||||||
mass_mailing_object = {}
|
|
||||||
for attr in ['name', 'sent', 'delivered', 'opened', 'replied', 'bounced']:
|
|
||||||
mass_mailing_object[attr] = getattr(mass_mailing, attr)
|
|
||||||
mass_mailing_results.append(mass_mailing_object)
|
|
||||||
results[campaign.id] = mass_mailing_results
|
|
||||||
return results
|
|
||||||
|
|
||||||
_columns = {
|
|
||||||
'name': fields.char(
|
|
||||||
'Campaign Name', required=True,
|
|
||||||
),
|
|
||||||
'user_id': fields.many2one(
|
|
||||||
'res.users', 'Responsible',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
'mass_mailing_ids': fields.one2many(
|
|
||||||
'mail.mass_mailing', 'mass_mailing_campaign_id',
|
|
||||||
'Mass Mailings',
|
|
||||||
),
|
|
||||||
'mass_mailing_kanban_ids': fields.function(
|
|
||||||
_get_mass_mailing_kanban_ids,
|
|
||||||
type='text', string='Mass Mailings (kanban data)',
|
|
||||||
help='This field has for purpose to gather data about mass mailings '
|
|
||||||
'to display them in kanban view as nested kanban views is not '
|
|
||||||
'possible currently',
|
|
||||||
),
|
|
||||||
'statistics_ids': fields.one2many(
|
|
||||||
'mail.mail.statistics', 'mass_mailing_campaign_id',
|
|
||||||
'Sent Emails',
|
|
||||||
),
|
|
||||||
'color': fields.integer('Color Index'),
|
|
||||||
# stat fields
|
|
||||||
'sent': fields.function(
|
|
||||||
_get_statistics,
|
|
||||||
string='Sent Emails',
|
|
||||||
type='integer', multi='_get_statistics'
|
|
||||||
),
|
|
||||||
'delivered': fields.function(
|
|
||||||
_get_statistics,
|
|
||||||
string='Delivered',
|
|
||||||
type='integer', multi='_get_statistics',
|
|
||||||
),
|
|
||||||
'opened': fields.function(
|
|
||||||
_get_statistics,
|
|
||||||
string='Opened',
|
|
||||||
type='integer', multi='_get_statistics',
|
|
||||||
),
|
|
||||||
'replied': fields.function(
|
|
||||||
_get_statistics,
|
|
||||||
string='Replied',
|
|
||||||
type='integer', multi='_get_statistics'
|
|
||||||
),
|
|
||||||
'bounced': fields.function(
|
|
||||||
_get_statistics,
|
|
||||||
string='Bounced',
|
|
||||||
type='integer', multi='_get_statistics'
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
_defaults = {
|
|
||||||
'user_id': lambda self, cr, uid, ctx=None: uid,
|
|
||||||
}
|
|
||||||
|
|
||||||
def launch_mass_mailing_create_wizard(self, cr, uid, ids, context=None):
|
|
||||||
ctx = dict(context)
|
|
||||||
ctx.update({
|
|
||||||
'default_mass_mailing_campaign_id': ids[0],
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
'name': _('Create a Mass Mailing for the Campaign'),
|
|
||||||
'type': 'ir.actions.act_window',
|
|
||||||
'view_type': 'form',
|
|
||||||
'view_mode': 'form',
|
|
||||||
'res_model': 'mail.mass_mailing.create',
|
|
||||||
'views': [(False, 'form')],
|
|
||||||
'view_id': False,
|
|
||||||
'target': 'new',
|
|
||||||
'context': ctx,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class MassMailing(osv.Model):
|
|
||||||
""" MassMailing models a wave of emails for a mass mailign campaign.
|
|
||||||
A mass mailing is an occurence of sending emails. """
|
|
||||||
|
|
||||||
_name = 'mail.mass_mailing'
|
|
||||||
_description = 'Wave of sending emails'
|
|
||||||
# number of periods for tracking mail_mail statistics
|
|
||||||
_period_number = 6
|
|
||||||
_order = 'date DESC'
|
|
||||||
|
|
||||||
def __get_bar_values(self, cr, uid, id, obj, domain, read_fields, value_field, groupby_field, context=None):
|
|
||||||
""" Generic method to generate data for bar chart values using SparklineBarWidget.
|
|
||||||
This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).
|
|
||||||
|
|
||||||
:param obj: the target model (i.e. crm_lead)
|
|
||||||
:param domain: the domain applied to the read_group
|
|
||||||
:param list read_fields: the list of fields to read in the read_group
|
|
||||||
:param str value_field: the field used to compute the value of the bar slice
|
|
||||||
:param str groupby_field: the fields used to group
|
|
||||||
|
|
||||||
:return list section_result: a list of dicts: [
|
|
||||||
{ 'value': (int) bar_column_value,
|
|
||||||
'tootip': (str) bar_column_tooltip,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
date_begin = datetime.strptime(self.browse(cr, uid, id, context=context).date, tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
|
|
||||||
section_result = [{'value': 0,
|
|
||||||
'tooltip': (date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y'),
|
|
||||||
} for i in range(0, self._period_number)]
|
|
||||||
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
|
|
||||||
field_col_info = obj._all_columns.get(groupby_field.split(':')[0])
|
|
||||||
pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field_col_info.column._type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
|
|
||||||
for group in group_obj:
|
|
||||||
group_begin_date = datetime.strptime(group['__domain'][0][2], pattern).date()
|
|
||||||
timedelta = relativedelta.relativedelta(group_begin_date, date_begin)
|
|
||||||
section_result[timedelta.days] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field)}
|
|
||||||
return section_result
|
|
||||||
|
|
||||||
def _get_daily_statistics(self, cr, uid, ids, field_name, arg, context=None):
|
|
||||||
""" Get the daily statistics of the mass mailing. This is done by a grouping
|
|
||||||
on opened and replied fields. Using custom format in context, we obtain
|
|
||||||
results for the next 6 days following the mass mailing date. """
|
|
||||||
obj = self.pool['mail.mail.statistics']
|
|
||||||
res = {}
|
|
||||||
for id in ids:
|
|
||||||
res[id] = {}
|
|
||||||
date_begin = datetime.strptime(self.browse(cr, uid, id, context=context).date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
|
||||||
date_end = date_begin + relativedelta.relativedelta(days=self._period_number - 1)
|
|
||||||
date_begin_str = date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
|
||||||
date_end_str = date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
|
||||||
domain = [('mass_mailing_id', '=', id), ('opened', '>=', date_begin_str), ('opened', '<=', date_end_str)]
|
|
||||||
res[id]['opened_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['opened'], 'opened_count', 'opened:day', context=context)
|
|
||||||
domain = [('mass_mailing_id', '=', id), ('replied', '>=', date_begin_str), ('replied', '<=', date_end_str)]
|
|
||||||
res[id]['replied_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['replied'], 'replied_count', 'replied:day', context=context)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def _get_statistics(self, cr, uid, ids, name, arg, context=None):
|
|
||||||
""" Compute statistics of the mass mailing campaign """
|
|
||||||
results = dict.fromkeys(ids, False)
|
|
||||||
for mass_mailing in self.browse(cr, uid, ids, context=context):
|
|
||||||
results[mass_mailing.id] = {
|
|
||||||
'sent': len(mass_mailing.statistics_ids),
|
|
||||||
'delivered': len([stat for stat in mass_mailing.statistics_ids if not stat.bounced]), # mail.state == 'sent' and
|
|
||||||
'opened': len([stat for stat in mass_mailing.statistics_ids if stat.opened]),
|
|
||||||
'replied': len([stat for stat in mass_mailing.statistics_ids if stat.replied]),
|
|
||||||
'bounced': len([stat for stat in mass_mailing.statistics_ids if stat.bounced]),
|
|
||||||
}
|
|
||||||
return results
|
|
||||||
|
|
||||||
_columns = {
|
|
||||||
'name': fields.char('Name', required=True),
|
|
||||||
'mass_mailing_campaign_id': fields.many2one(
|
|
||||||
'mail.mass_mailing.campaign', 'Mass Mailing Campaign',
|
|
||||||
ondelete='cascade', required=True,
|
|
||||||
),
|
|
||||||
'template_id': fields.many2one(
|
|
||||||
'email.template', 'Email Template',
|
|
||||||
ondelete='set null',
|
|
||||||
),
|
|
||||||
'domain': fields.char('Domain'),
|
|
||||||
'date': fields.datetime('Date'),
|
|
||||||
'color': fields.related(
|
|
||||||
'mass_mailing_campaign_id', 'color',
|
|
||||||
type='integer', string='Color Index',
|
|
||||||
),
|
|
||||||
# statistics data
|
|
||||||
'statistics_ids': fields.one2many(
|
|
||||||
'mail.mail.statistics', 'mass_mailing_id',
|
|
||||||
'Emails Statistics',
|
|
||||||
),
|
|
||||||
'sent': fields.function(
|
|
||||||
_get_statistics,
|
|
||||||
string='Sent Emails',
|
|
||||||
type='integer', multi='_get_statistics'
|
|
||||||
),
|
|
||||||
'delivered': fields.function(
|
|
||||||
_get_statistics,
|
|
||||||
string='Delivered',
|
|
||||||
type='integer', multi='_get_statistics',
|
|
||||||
),
|
|
||||||
'opened': fields.function(
|
|
||||||
_get_statistics,
|
|
||||||
string='Opened',
|
|
||||||
type='integer', multi='_get_statistics',
|
|
||||||
),
|
|
||||||
'replied': fields.function(
|
|
||||||
_get_statistics,
|
|
||||||
string='Replied',
|
|
||||||
type='integer', multi='_get_statistics'
|
|
||||||
),
|
|
||||||
'bounced': fields.function(
|
|
||||||
_get_statistics,
|
|
||||||
string='Bounce',
|
|
||||||
type='integer', multi='_get_statistics'
|
|
||||||
),
|
|
||||||
# monthly ratio
|
|
||||||
'opened_monthly': fields.function(
|
|
||||||
_get_daily_statistics,
|
|
||||||
string='Opened',
|
|
||||||
type='char', multi='_get_daily_statistics',
|
|
||||||
),
|
|
||||||
'replied_monthly': fields.function(
|
|
||||||
_get_daily_statistics,
|
|
||||||
string='Replied',
|
|
||||||
type='char', multi='_get_daily_statistics',
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
_defaults = {
|
|
||||||
'date': fields.datetime.now,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class MailMailStats(osv.Model):
|
|
||||||
""" MailMailStats models the statistics collected about emails. Those statistics
|
|
||||||
are stored in a separated model and table to avoid bloating the mail_mail table
|
|
||||||
with statistics values. This also allows to delete emails send with mass mailing
|
|
||||||
without loosing the statistics about them. """
|
|
||||||
|
|
||||||
_name = 'mail.mail.statistics'
|
|
||||||
_description = 'Email Statistics'
|
|
||||||
_rec_name = 'message_id'
|
|
||||||
_order = 'message_id'
|
|
||||||
|
|
||||||
_columns = {
|
|
||||||
'mail_mail_id': fields.integer(
|
|
||||||
'Mail ID',
|
|
||||||
help='ID of the related mail_mail. This field is an integer field because'
|
|
||||||
'the related mail_mail can be deleted separately from its statistics.'
|
|
||||||
),
|
|
||||||
'message_id': fields.char(
|
|
||||||
'Message-ID',
|
|
||||||
),
|
|
||||||
'model': fields.char(
|
|
||||||
'Document model',
|
|
||||||
),
|
|
||||||
'res_id': fields.integer(
|
|
||||||
'Document ID',
|
|
||||||
),
|
|
||||||
# campaign / wave data
|
|
||||||
'mass_mailing_id': fields.many2one(
|
|
||||||
'mail.mass_mailing', 'Mass Mailing',
|
|
||||||
ondelete='set null',
|
|
||||||
),
|
|
||||||
'mass_mailing_campaign_id': fields.related(
|
|
||||||
'mass_mailing_id', 'mass_mailing_campaign_id',
|
|
||||||
type='many2one', ondelete='set null',
|
|
||||||
relation='mail.mass_mailing.campaign',
|
|
||||||
string='Mass Mailing Campaign',
|
|
||||||
store=True, readonly=True,
|
|
||||||
),
|
|
||||||
'template_id': fields.related(
|
|
||||||
'mass_mailing_id', 'template_id',
|
|
||||||
type='many2one', ondelete='set null',
|
|
||||||
relation='email.template',
|
|
||||||
string='Email Template',
|
|
||||||
store=True, readonly=True,
|
|
||||||
),
|
|
||||||
# Bounce and tracking
|
|
||||||
'opened': fields.datetime(
|
|
||||||
'Opened',
|
|
||||||
help='Date when this email has been opened for the first time.'),
|
|
||||||
'replied': fields.datetime(
|
|
||||||
'Replied',
|
|
||||||
help='Date when this email has been replied for the first time.'),
|
|
||||||
'bounced': fields.datetime(
|
|
||||||
'Bounced',
|
|
||||||
help='Date when this email has bounced.'
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
def set_opened(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
|
||||||
""" Set as opened """
|
|
||||||
if not ids and mail_mail_ids:
|
|
||||||
ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
|
|
||||||
elif not ids and mail_message_ids:
|
|
||||||
ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
|
|
||||||
else:
|
|
||||||
ids = []
|
|
||||||
for stat in self.browse(cr, uid, ids, context=context):
|
|
||||||
if not stat.opened:
|
|
||||||
self.write(cr, uid, [stat.id], {'opened': fields.datetime.now()}, context=context)
|
|
||||||
return ids
|
|
||||||
|
|
||||||
def set_replied(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
|
||||||
""" Set as replied """
|
|
||||||
if not ids and mail_mail_ids:
|
|
||||||
ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
|
|
||||||
elif not ids and mail_message_ids:
|
|
||||||
ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
|
|
||||||
else:
|
|
||||||
ids = []
|
|
||||||
for stat in self.browse(cr, uid, ids, context=context):
|
|
||||||
if not stat.replied:
|
|
||||||
self.write(cr, uid, [stat.id], {'replied': fields.datetime.now()}, context=context)
|
|
||||||
return ids
|
|
||||||
|
|
||||||
def set_bounced(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
|
||||||
""" Set as bounced """
|
|
||||||
if not ids and mail_mail_ids:
|
|
||||||
ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
|
|
||||||
elif not ids and mail_message_ids:
|
|
||||||
ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
|
|
||||||
else:
|
|
||||||
ids = []
|
|
||||||
for stat in self.browse(cr, uid, ids, context=context):
|
|
||||||
if not stat.bounced:
|
|
||||||
self.write(cr, uid, [stat.id], {'bounced': fields.datetime.now()}, context=context)
|
|
||||||
return ids
|
|
|
@ -1,82 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<openerp>
|
|
||||||
<!-- <data noupdate="1"> -->
|
|
||||||
<data>
|
|
||||||
|
|
||||||
<record id="mass_mail_template_1" model="email.template">
|
|
||||||
<field name="name">Partner Newsletter 1</field>
|
|
||||||
<field name="model_id" ref="base.model_res_partner"/>
|
|
||||||
<field name="auto_delete" eval="False"/>
|
|
||||||
<field name="partner_to">${object.id}</field>
|
|
||||||
<field name="body_html"><![CDATA[<p>Hello</p>]]></field>
|
|
||||||
</record>
|
|
||||||
<record id="mass_mail_template_2" model="email.template">
|
|
||||||
<field name="name">Partner Newsletter 2</field>
|
|
||||||
<field name="model_id" ref="base.model_res_partner"/>
|
|
||||||
<field name="auto_delete" eval="False"/>
|
|
||||||
<field name="partner_to">${object.id}</field>
|
|
||||||
<field name="body_html"><![CDATA[<p>Hello</p>]]></field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="mass_mail_campaign_1" model="mail.mass_mailing.campaign">
|
|
||||||
<field name="name">Partners Newsletter</field>
|
|
||||||
<field name="user_id" eval="ref('base.user_root')"/>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="mass_mail_1" model="mail.mass_mailing">
|
|
||||||
<field name="name">First Newsletter</field>
|
|
||||||
<field name="template_id" eval="ref('mass_mail_template_1')"/>
|
|
||||||
<field name="date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
||||||
<field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
|
||||||
</record>
|
|
||||||
<record id="mass_mail_2" model="mail.mass_mailing">
|
|
||||||
<field name="name">Second Newsletter</field>
|
|
||||||
<field name="template_id" eval="ref('mass_mail_template_2')"/>
|
|
||||||
<field name="date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
||||||
<field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="mass_mail_email_1" model="mail.mail.statistics">
|
|
||||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
|
||||||
<field name="message_id">1111000@OpenERP.com</field>
|
|
||||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
||||||
<field name="replied" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
||||||
</record>
|
|
||||||
<record id="mass_mail_email_2" model="mail.mail.statistics">
|
|
||||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
|
||||||
<field name="message_id">1111001@OpenERP.com</field>
|
|
||||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
||||||
<field name="replied" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
||||||
</record>
|
|
||||||
<record id="mass_mail_email_3" model="mail.mail.statistics">
|
|
||||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
|
||||||
<field name="message_id">1111002@OpenERP.com</field>
|
|
||||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
||||||
</record>
|
|
||||||
<record id="mass_mail_email_4" model="mail.mail.statistics">
|
|
||||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
|
||||||
<field name="message_id">1111003@OpenERP.com</field>
|
|
||||||
</record>
|
|
||||||
<record id="mass_mail_email_5" model="mail.mail.statistics">
|
|
||||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
|
||||||
<field name="message_id">1111004@OpenERP.com</field>
|
|
||||||
<field name="bounced" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="mass_mail_email_2_1" model="mail.mail.statistics">
|
|
||||||
<field name="mass_mailing_id" eval="ref('mass_mail_2')"/>
|
|
||||||
<field name="message_id">1111005@OpenERP.com</field>
|
|
||||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
||||||
</record>
|
|
||||||
<record id="mass_mail_email_2_2" model="mail.mail.statistics">
|
|
||||||
<field name="mass_mailing_id" eval="ref('mass_mail_2')"/>
|
|
||||||
<field name="message_id">1111006@OpenERP.com</field>
|
|
||||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
|
||||||
</record>
|
|
||||||
<record id="mass_mail_email_2_3" model="mail.mail.statistics">
|
|
||||||
<field name="mass_mailing_id" eval="ref('mass_mail_2')"/>
|
|
||||||
<field name="message_id">1111007@OpenERP.com</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
</data>
|
|
||||||
</openerp>
|
|
|
@ -1,379 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<openerp>
|
|
||||||
<data>
|
|
||||||
|
|
||||||
<!-- MASS MAILING !-->
|
|
||||||
<record model="ir.ui.view" id="view_mail_mass_mailing_search">
|
|
||||||
<field name="name">mail.mass_mailing.search</field>
|
|
||||||
<field name="model">mail.mass_mailing</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<search string="Mass Mailings">
|
|
||||||
<field name="name" string="Mailings"/>
|
|
||||||
<field name="mass_mailing_campaign_id"/>
|
|
||||||
<field name="template_id"/>
|
|
||||||
<group expand="0" string="Group By...">
|
|
||||||
<filter string="Campaign" name="group_mass_mailing_campaign_id"
|
|
||||||
context="{'group_by': 'mass_mailing_campaign_id'}"/>
|
|
||||||
<filter string="Template" name="group_template_id"
|
|
||||||
context="{'group_by': 'template_id'}"/>
|
|
||||||
</group>
|
|
||||||
</search>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record model="ir.ui.view" id="view_mail_mass_mailing_tree">
|
|
||||||
<field name="name">mail.mass_mailing.tree</field>
|
|
||||||
<field name="model">mail.mass_mailing</field>
|
|
||||||
<field name="priority">10</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<tree string="Mass Mailings">
|
|
||||||
<field name="name"/>
|
|
||||||
<field name="sent"/>
|
|
||||||
<field name="delivered"/>
|
|
||||||
<field name="opened"/>
|
|
||||||
<field name="replied"/>
|
|
||||||
<field name="mass_mailing_campaign_id" invisible="1"/>
|
|
||||||
<field name="template_id" invisible="1"/>
|
|
||||||
</tree>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record model="ir.ui.view" id="view_mail_mass_mailing_form">
|
|
||||||
<field name="name">mail.mass_mailing.form</field>
|
|
||||||
<field name="model">mail.mass_mailing</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form string="Mass Mailing" version="7.0">
|
|
||||||
<sheet>
|
|
||||||
<group>
|
|
||||||
<group>
|
|
||||||
<field name="name"/>
|
|
||||||
<field name="mass_mailing_campaign_id" readonly="True"/>
|
|
||||||
</group>
|
|
||||||
<group>
|
|
||||||
<field name="template_id"/>
|
|
||||||
<field name="domain"/>
|
|
||||||
<field name="date"/>
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
<group string="Email Statistics">
|
|
||||||
<field name="statistics_ids" nolabel="1" colspan="2"/>
|
|
||||||
<group>
|
|
||||||
<field name="sent"/>
|
|
||||||
<field name="opened"/>
|
|
||||||
<field name="bounced"/>
|
|
||||||
</group>
|
|
||||||
<group>
|
|
||||||
<field name="delivered"/>
|
|
||||||
<field name="replied"/>
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
</sheet>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record model="ir.ui.view" id="view_mail_mass_mailing_kanban">
|
|
||||||
<field name="name">mail.mass_mailing.kanban</field>
|
|
||||||
<field name="model">mail.mass_mailing</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<kanban>
|
|
||||||
<field name='color'/>
|
|
||||||
<templates>
|
|
||||||
<t t-name="kanban-box">
|
|
||||||
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_kanban_mass_mailing oe_kanban_mass_mailing_segment">
|
|
||||||
<div class="oe_kanban_content">
|
|
||||||
<div>
|
|
||||||
<h3>
|
|
||||||
<field name="name"/>
|
|
||||||
</h3>
|
|
||||||
<p style="margin-left: 10px; margin-top: 8px;">
|
|
||||||
Sent: <field name="date"/><br />
|
|
||||||
Campaign: <field name="mass_mailing_campaign_id"/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="oe_mail_stats">
|
|
||||||
<span class="oe_mail_result"><field name="sent"/></span><br />
|
|
||||||
Sent
|
|
||||||
</p>
|
|
||||||
<p class="oe_mail_stats">
|
|
||||||
<span class="oe_mail_result"><field name="delivered"/></span><br />
|
|
||||||
Delivered
|
|
||||||
</p>
|
|
||||||
<p class="oe_mail_stats">
|
|
||||||
<span class="oe_mail_result"><field name="opened"/></span><br />
|
|
||||||
Opened
|
|
||||||
</p>
|
|
||||||
<p class="oe_mail_stats">
|
|
||||||
<span class="oe_mail_result"><field name="replied"/></span><br />
|
|
||||||
Replied
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="oe_sparkline_container">
|
|
||||||
<h4 class="oe_sparkline_bar_title">Opened</h4><br />
|
|
||||||
<field name="opened_monthly" widget="sparkline_bar" options="{'height': '50px', 'barWidth': 10, 'barSpacing': 5}"/>
|
|
||||||
</div>
|
|
||||||
<div class="oe_sparkline_container">
|
|
||||||
<h4 class="oe_sparkline_bar_title">Replied</h4><br />
|
|
||||||
<field name="replied_monthly" widget="sparkline_bar" options="{'height': '50px', 'barWidth': 10, 'barSpacing': 5}"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="oe_clear"></div>
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
</templates>
|
|
||||||
</kanban>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="action_view_mass_mailings" model="ir.actions.act_window">
|
|
||||||
<field name="name">Mass Mailings</field>
|
|
||||||
<field name="res_model">mail.mass_mailing</field>
|
|
||||||
<field name="view_type">form</field>
|
|
||||||
<field name="view_mode">kanban,tree,form</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="action_view_mass_mailings_from_campaign" model="ir.actions.act_window">
|
|
||||||
<field name="name">Mass Mailings</field>
|
|
||||||
<field name="res_model">mail.mass_mailing</field>
|
|
||||||
<field name="view_type">form</field>
|
|
||||||
<field name="view_mode">kanban,tree,form</field>
|
|
||||||
<field name="context">{
|
|
||||||
'search_default_mass_mailing_campaign_id': [active_id],
|
|
||||||
'default_mass_mailing_campaign_id': active_id,
|
|
||||||
}
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- MASS MAILING CAMPAIGNS !-->
|
|
||||||
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_search">
|
|
||||||
<field name="name">mail.mass_mailing.campaign.search</field>
|
|
||||||
<field name="model">mail.mass_mailing.campaign</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<search string="Mass Mailing Campaigns">
|
|
||||||
<field name="name" string="Campaigns"/>
|
|
||||||
<field name="user_id"/>
|
|
||||||
<group expand="0" string="Group By...">
|
|
||||||
<filter string="Responsibles" name="group_user_id"
|
|
||||||
context="{'group_by': 'user_id'}"/>
|
|
||||||
</group>
|
|
||||||
</search>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_tree">
|
|
||||||
<field name="name">mail.mass_mailing.campaign.tree</field>
|
|
||||||
<field name="model">mail.mass_mailing.campaign</field>
|
|
||||||
<field name="priority">10</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<tree string="Mass Mailing Campaigns">
|
|
||||||
<field name="name"/>
|
|
||||||
<field name="user_id"/>
|
|
||||||
</tree>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_form">
|
|
||||||
<field name="name">mail.mass_mailing.campaign.form</field>
|
|
||||||
<field name="model">mail.mass_mailing.campaign</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form string="Mass Mailing Campaign" version="7.0">
|
|
||||||
<header>
|
|
||||||
<button name="launch_mass_mailing_create_wizard" type="object"
|
|
||||||
class="oe_highlight" string="Create a New Mailing"/>
|
|
||||||
</header>
|
|
||||||
<sheet>
|
|
||||||
<group>
|
|
||||||
<field name="name"/>
|
|
||||||
<field name="user_id"/>
|
|
||||||
</group>
|
|
||||||
<group>
|
|
||||||
<group>
|
|
||||||
<field name="sent"/>
|
|
||||||
<field name="opened"/>
|
|
||||||
<field name="bounced"/>
|
|
||||||
</group>
|
|
||||||
<group>
|
|
||||||
<field name="delivered"/>
|
|
||||||
<field name="replied"/>
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
<group>
|
|
||||||
<field name="mass_mailing_ids" readonly="1"/>
|
|
||||||
</group>
|
|
||||||
</sheet>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_kanban">
|
|
||||||
<field name="name">mail.mass_mailing.campaign.kanban</field>
|
|
||||||
<field name="model">mail.mass_mailing.campaign</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<kanban>
|
|
||||||
<field name="mass_mailing_kanban_ids"/>
|
|
||||||
<field name='sent'/>
|
|
||||||
<field name='color'/>
|
|
||||||
<templates>
|
|
||||||
<t t-name="mass_mailing.mass_mailing">
|
|
||||||
<div class="oe_mass_mailings">
|
|
||||||
<div>
|
|
||||||
<a name="%(action_view_mass_mailings_from_campaign)d" type="action">
|
|
||||||
<h4><t t-raw="mass_mailing.name"/></h4>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="oe_mail_stats">
|
|
||||||
<span class="oe_mail_result"><t t-raw="mass_mailing.sent"/></span><br />
|
|
||||||
Sent
|
|
||||||
</p>
|
|
||||||
<p class="oe_mail_stats">
|
|
||||||
<span class="oe_mail_result"><t t-raw="mass_mailing.delivered"/></span><br />
|
|
||||||
Delivered
|
|
||||||
</p>
|
|
||||||
<p class="oe_mail_stats">
|
|
||||||
<span class="oe_mail_result"><t t-raw="mass_mailing.opened"/></span><br />
|
|
||||||
Opened
|
|
||||||
</p>
|
|
||||||
<p class="oe_mail_stats">
|
|
||||||
<span class="oe_mail_result"><t t-raw="mass_mailing.replied"/></span><br />
|
|
||||||
Replied
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
<t t-name="kanban-box">
|
|
||||||
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_kanban_mass_mailing oe_kanban_mass_mailing_campaign">
|
|
||||||
<div class="oe_dropdown_toggle oe_dropdown_kanban">
|
|
||||||
<span class="oe_e">i</span>
|
|
||||||
<ul class="oe_dropdown_menu">
|
|
||||||
<t t-if="widget.view.is_action_enabled('edit')">
|
|
||||||
<li><a type="edit">Settings</a></li>
|
|
||||||
</t>
|
|
||||||
<t t-if="widget.view.is_action_enabled('edit')">
|
|
||||||
<li><a name="%(action_mail_mass_mailing_create)d" type="action">New Wave</a></li>
|
|
||||||
</t>
|
|
||||||
<t t-if="widget.view.is_action_enabled('delete')">
|
|
||||||
<li><a type="delete">Delete</a></li>
|
|
||||||
</t>
|
|
||||||
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="oe_kanban_content">
|
|
||||||
<h3>
|
|
||||||
<field name="name"/>
|
|
||||||
</h3>
|
|
||||||
<div>
|
|
||||||
<field name="delivered" widget="gauge" style="width:160px; height: 120px;"
|
|
||||||
options="{'max_field': 'sent'}"/>
|
|
||||||
<field name="opened" widget="gauge" style="width:160px; height: 120px;"
|
|
||||||
options="{'max_field': 'sent'}"/>
|
|
||||||
<field name="replied" widget="gauge" style="width:160px; height: 120px;"
|
|
||||||
options="{'max_field': 'sent'}"/>
|
|
||||||
</div>
|
|
||||||
<t t-foreach='record.mass_mailing_kanban_ids.value' t-as='mass_mailing'>
|
|
||||||
<t t-call="mass_mailing.mass_mailing"/>
|
|
||||||
</t>
|
|
||||||
</div>
|
|
||||||
<div class="oe_clear"></div>
|
|
||||||
</div>
|
|
||||||
</t>
|
|
||||||
</templates>
|
|
||||||
</kanban>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="action_view_mass_mailing_campaigns" model="ir.actions.act_window">
|
|
||||||
<field name="name">Mass Mailing Campaigns</field>
|
|
||||||
<field name="res_model">mail.mass_mailing.campaign</field>
|
|
||||||
<field name="view_type">form</field>
|
|
||||||
<field name="view_mode">kanban,tree,form</field>
|
|
||||||
<field name="help" type="html">
|
|
||||||
<p class="oe_view_nocontent_create">
|
|
||||||
Click to define a new mass mailing campaign.
|
|
||||||
</p><p>
|
|
||||||
Create a campaign to structure mass mailing and get analysis from email status.
|
|
||||||
</p>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- MAIL MAIL STATISTICS !-->
|
|
||||||
<record model="ir.ui.view" id="view_mail_mail_statistics_search">
|
|
||||||
<field name="name">mail.mail.statistics.search</field>
|
|
||||||
<field name="model">mail.mail.statistics</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<search string="Mail Statistics">
|
|
||||||
<field name="mail_mail_id"/>
|
|
||||||
<field name="message_id"/>
|
|
||||||
</search>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record model="ir.ui.view" id="view_mail_mail_statistics_tree">
|
|
||||||
<field name="name">mail.mail.statistics.tree</field>
|
|
||||||
<field name="model">mail.mail.statistics</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<tree string="Mail Statistics">
|
|
||||||
<field name="mail_mail_id"/>
|
|
||||||
<field name="message_id"/>
|
|
||||||
<field name="opened"/>
|
|
||||||
<field name="replied"/>
|
|
||||||
<field name="bounced"/>
|
|
||||||
</tree>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record model="ir.ui.view" id="view_mail_mail_statistics_form">
|
|
||||||
<field name="name">mail.mail.statistics.form</field>
|
|
||||||
<field name="model">mail.mail.statistics</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form string="Mail Statistics" version="7.0">
|
|
||||||
<group>
|
|
||||||
<group>
|
|
||||||
<field name="mail_mail_id"/>
|
|
||||||
<field name="message_id"/>
|
|
||||||
<field name="opened"/>
|
|
||||||
<field name="replied"/>
|
|
||||||
<field name="bounced"/>
|
|
||||||
</group>
|
|
||||||
<group>
|
|
||||||
<field name="mass_mailing_id"/>
|
|
||||||
<field name="mass_mailing_campaign_id"/>
|
|
||||||
<field name="template_id"/>
|
|
||||||
<field name="model"/>
|
|
||||||
<field name="res_id"/>
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="action_view_mail_mail_statistics" model="ir.actions.act_window">
|
|
||||||
<field name="name">Mail Statistics</field>
|
|
||||||
<field name="res_model">mail.mail.statistics</field>
|
|
||||||
<field name="view_type">form</field>
|
|
||||||
<field name="view_mode">tree,form</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Top menu item -->
|
|
||||||
<menuitem name="Marketing" id="base.marketing_menu" sequence="85" groups="base.group_user"/>
|
|
||||||
|
|
||||||
<!-- Add in marketing -->
|
|
||||||
<menuitem name="Mass Mailing" id="mass_mailing_campaign"
|
|
||||||
parent="base.marketing_menu" sequence="1"/>
|
|
||||||
<menuitem name="Campaigns" id="menu_email_campaigns"
|
|
||||||
parent="mass_mailing_campaign" sequence="1"
|
|
||||||
action="action_view_mass_mailing_campaigns"/>
|
|
||||||
<menuitem name="Mass Mailings" id="menu_email_mass_mailings"
|
|
||||||
parent="mass_mailing_campaign" sequence="2"
|
|
||||||
action="action_view_mass_mailings"/>
|
|
||||||
|
|
||||||
<!-- Add in Technical/Email -->
|
|
||||||
<menuitem name="Mail Statistics" id="menu_email_statistics"
|
|
||||||
parent="base.menu_email" sequence="50"
|
|
||||||
action="action_view_mail_mail_statistics"/>
|
|
||||||
|
|
||||||
</data>
|
|
||||||
</openerp>
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import mass_mailing
|
||||||
|
import mass_mailing_stats
|
||||||
|
import mail_mail
|
||||||
|
import mail_thread
|
||||||
|
import res_config
|
|
@ -19,7 +19,8 @@
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
from urlparse import urljoin
|
import urllib
|
||||||
|
import urlparse
|
||||||
|
|
||||||
from openerp import tools
|
from openerp import tools
|
||||||
from openerp import SUPERUSER_ID
|
from openerp import SUPERUSER_ID
|
||||||
|
@ -32,6 +33,7 @@ class MailMail(osv.Model):
|
||||||
_inherit = ['mail.mail']
|
_inherit = ['mail.mail']
|
||||||
|
|
||||||
_columns = {
|
_columns = {
|
||||||
|
'mailing_id': fields.many2one('mail.mass_mailing', 'Mass Mailing'),
|
||||||
'statistics_ids': fields.one2many(
|
'statistics_ids': fields.one2many(
|
||||||
'mail.mail.statistics', 'mail_mail_id',
|
'mail.mail.statistics', 'mail_mail_id',
|
||||||
string='Statistics',
|
string='Statistics',
|
||||||
|
@ -50,9 +52,24 @@ class MailMail(osv.Model):
|
||||||
|
|
||||||
def _get_tracking_url(self, cr, uid, mail, partner=None, context=None):
|
def _get_tracking_url(self, cr, uid, mail, partner=None, context=None):
|
||||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||||
track_url = urljoin(base_url, 'mail/track/%d/blank.gif' % mail.id)
|
track_url = urlparse.urljoin(
|
||||||
|
base_url, 'mail/track/%(mail_id)s/blank.gif?%(params)s' % {
|
||||||
|
'mail_id': mail.id,
|
||||||
|
'params': urllib.urlencode({'db': cr.dbname})
|
||||||
|
}
|
||||||
|
)
|
||||||
return '<img src="%s" alt=""/>' % track_url
|
return '<img src="%s" alt=""/>' % track_url
|
||||||
|
|
||||||
|
def _get_unsubscribe_url(self, cr, uid, mail, email_to, msg=None, context=None):
|
||||||
|
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||||
|
url = urlparse.urljoin(
|
||||||
|
base_url, 'mail/mailing/%(mailing_id)s/unsubscribe?%(params)s' % {
|
||||||
|
'mailing_id': mail.mailing_id.id,
|
||||||
|
'params': urllib.urlencode({'db': cr.dbname, 'res_id': mail.res_id, 'email': email_to})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return '<small><a href="%s">%s</a></small>' % (url, msg or 'Click to unsubscribe')
|
||||||
|
|
||||||
def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
|
def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
|
||||||
""" Override to add the tracking URL to the body. """
|
""" Override to add the tracking URL to the body. """
|
||||||
body = super(MailMail, self).send_get_mail_body(cr, uid, mail, partner=partner, context=context)
|
body = super(MailMail, self).send_get_mail_body(cr, uid, mail, partner=partner, context=context)
|
||||||
|
@ -63,3 +80,19 @@ class MailMail(osv.Model):
|
||||||
if tracking_url:
|
if tracking_url:
|
||||||
body = tools.append_content_to_html(body, tracking_url, plaintext=False, container_tag='div')
|
body = tools.append_content_to_html(body, tracking_url, plaintext=False, container_tag='div')
|
||||||
return body
|
return body
|
||||||
|
|
||||||
|
def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
|
||||||
|
res = super(MailMail, self).send_get_email_dict(cr, uid, mail, partner, context=context)
|
||||||
|
if mail.mailing_id and res.get('body') and res.get('email_to'):
|
||||||
|
email_to = tools.email_split(res.get('email_to')[0])
|
||||||
|
unsubscribe_url = self._get_unsubscribe_url(cr, uid, mail, email_to, context=context)
|
||||||
|
if unsubscribe_url:
|
||||||
|
res['body'] = tools.append_content_to_html(res['body'], unsubscribe_url, plaintext=False, container_tag='p')
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _postprocess_sent_message(self, cr, uid, mail, context=None, mail_sent=True):
|
||||||
|
if mail_sent is True and mail.statistics_ids:
|
||||||
|
self.pool['mail.mail.statistics'].write(cr, uid, [s.id for s in mail.statistics_ids], {'sent': fields.datetime.now()}, context=context)
|
||||||
|
elif mail_sent is False and mail.statistics_ids:
|
||||||
|
self.pool['mail.mail.statistics'].write(cr, uid, [s.id for s in mail.statistics_ids], {'exception': fields.datetime.now()}, context=context)
|
||||||
|
return super(MailMail, self)._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=mail_sent)
|
|
@ -29,7 +29,7 @@ from openerp.osv import osv
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MailThread(osv.Model):
|
class MailThread(osv.AbstractModel):
|
||||||
""" Update MailThread to add the feature of bounced emails and replied emails
|
""" Update MailThread to add the feature of bounced emails and replied emails
|
||||||
in message_process. """
|
in message_process. """
|
||||||
_name = 'mail.thread'
|
_name = 'mail.thread'
|
|
@ -0,0 +1,571 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from dateutil import relativedelta
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
import urllib
|
||||||
|
import urlparse
|
||||||
|
|
||||||
|
from openerp import tools
|
||||||
|
from openerp.tools.safe_eval import safe_eval as eval
|
||||||
|
from openerp.tools.translate import _
|
||||||
|
from openerp.osv import osv, fields
|
||||||
|
|
||||||
|
|
||||||
|
class MassMailingCategory(osv.Model):
|
||||||
|
"""Model of categories of mass mailing, i.e. marketing, newsletter, ... """
|
||||||
|
_name = 'mail.mass_mailing.category'
|
||||||
|
_description = 'Mass Mailing Category'
|
||||||
|
_order = 'name'
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'name': fields.char('Name', required=True),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MassMailingContact(osv.Model):
|
||||||
|
"""Model of a contact. This model is different from the partner model
|
||||||
|
because it holds only some basic information: name, email. The purpose is to
|
||||||
|
be able to deal with large contact list to email without bloating the partner
|
||||||
|
base."""
|
||||||
|
_name = 'mail.mass_mailing.contact'
|
||||||
|
_description = 'Mass Mailing Contact'
|
||||||
|
_order = 'email'
|
||||||
|
_rec_name = 'email'
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'name': fields.char('Name'),
|
||||||
|
'email': fields.char('Email', required=True),
|
||||||
|
'create_date': fields.datetime('Create Date'),
|
||||||
|
'list_id': fields.many2one(
|
||||||
|
'mail.mass_mailing.list', string='Mailing List',
|
||||||
|
ondelete='cascade', required=True,
|
||||||
|
),
|
||||||
|
'opt_out': fields.boolean('Opt Out', help='The contact has chosen not to receive mails anymore from this list'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_latest_list(self, cr, uid, context={}):
|
||||||
|
lid = self.pool.get('mail.mass_mailing.list').search(cr, uid, [], limit=1, order='id desc', context=context)
|
||||||
|
return lid and lid[0] or False
|
||||||
|
|
||||||
|
_defaults = {
|
||||||
|
'list_id': _get_latest_list
|
||||||
|
}
|
||||||
|
|
||||||
|
def name_create(self, cr, uid, name, context=None):
|
||||||
|
name, email = self.pool['res.partner']._parse_partner_name(name, context=context)
|
||||||
|
if name and not email:
|
||||||
|
email = name
|
||||||
|
if email and not name:
|
||||||
|
name = email
|
||||||
|
rec_id = self.create(cr, uid, {'name': name, 'email': email}, context=context)
|
||||||
|
return self.name_get(cr, uid, [rec_id], context)[0]
|
||||||
|
|
||||||
|
|
||||||
|
class MassMailingList(osv.Model):
|
||||||
|
"""Model of a contact list. """
|
||||||
|
_name = 'mail.mass_mailing.list'
|
||||||
|
_order = 'name'
|
||||||
|
_description = 'Mailing List'
|
||||||
|
|
||||||
|
def _get_contact_nbr(self, cr, uid, ids, name, arg, context=None):
|
||||||
|
result = dict.fromkeys(ids, 0)
|
||||||
|
Contacts = self.pool.get('mail.mass_mailing.contact')
|
||||||
|
for group in Contacts.read_group(cr, uid, [('list_id', 'in', ids), ('opt_out', '!=', True)], ['list_id'], ['list_id'], context=context):
|
||||||
|
result[group['list_id'][0]] = group['list_id_count']
|
||||||
|
return result
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'name': fields.char('Mailing List', required=True),
|
||||||
|
'contact_nbr': fields.function(
|
||||||
|
_get_contact_nbr, type='integer',
|
||||||
|
string='Number of Contacts',
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MassMailingStage(osv.Model):
|
||||||
|
"""Stage for mass mailing campaigns. """
|
||||||
|
_name = 'mail.mass_mailing.stage'
|
||||||
|
_description = 'Mass Mailing Campaign Stage'
|
||||||
|
_order = 'sequence'
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'name': fields.char('Name', required=True, translate=True),
|
||||||
|
'sequence': fields.integer('Sequence'),
|
||||||
|
}
|
||||||
|
|
||||||
|
_defaults = {
|
||||||
|
'sequence': 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MassMailingCampaign(osv.Model):
|
||||||
|
"""Model of mass mailing campaigns. """
|
||||||
|
_name = "mail.mass_mailing.campaign"
|
||||||
|
_description = 'Mass Mailing Campaign'
|
||||||
|
|
||||||
|
def _get_statistics(self, cr, uid, ids, name, arg, context=None):
|
||||||
|
""" Compute statistics of the mass mailing campaign """
|
||||||
|
Statistics = self.pool['mail.mail.statistics']
|
||||||
|
results = dict.fromkeys(ids, False)
|
||||||
|
for cid in ids:
|
||||||
|
stat_ids = Statistics.search(cr, uid, [('mass_mailing_campaign_id', '=', cid)], context=context)
|
||||||
|
stats = Statistics.browse(cr, uid, stat_ids, context=context)
|
||||||
|
results[cid] = {
|
||||||
|
'total': len(stats),
|
||||||
|
'failed': len([s for s in stats if not s.scheduled is False and s.sent is False and not s.exception is False]),
|
||||||
|
'scheduled': len([s for s in stats if not s.scheduled is False and s.sent is False and s.exception is False]),
|
||||||
|
'sent': len([s for s in stats if not s.sent is False]),
|
||||||
|
'opened': len([s for s in stats if not s.opened is False]),
|
||||||
|
'replied': len([s for s in stats if not s.replied is False]),
|
||||||
|
'bounced': len([s for s in stats if not s.bounced is False]),
|
||||||
|
}
|
||||||
|
results[cid]['delivered'] = results[cid]['sent'] - results[cid]['bounced']
|
||||||
|
results[cid]['received_ratio'] = 100.0 * results[cid]['delivered'] / (results[cid]['total'] or 1)
|
||||||
|
results[cid]['opened_ratio'] = 100.0 * results[cid]['opened'] / (results[cid]['total'] or 1)
|
||||||
|
results[cid]['replied_ratio'] = 100.0 * results[cid]['replied'] / (results[cid]['total'] or 1)
|
||||||
|
return results
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'name': fields.char('Name', required=True),
|
||||||
|
'stage_id': fields.many2one('mail.mass_mailing.stage', 'Stage', required=True),
|
||||||
|
'user_id': fields.many2one(
|
||||||
|
'res.users', 'Responsible',
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
'category_ids': fields.many2many(
|
||||||
|
'mail.mass_mailing.category', 'mail_mass_mailing_category_rel',
|
||||||
|
'category_id', 'campaign_id', string='Categories'),
|
||||||
|
'mass_mailing_ids': fields.one2many(
|
||||||
|
'mail.mass_mailing', 'mass_mailing_campaign_id',
|
||||||
|
'Mass Mailings',
|
||||||
|
),
|
||||||
|
'unique_ab_testing': fields.boolean(
|
||||||
|
'AB Testing',
|
||||||
|
help='If checked, recipients will be mailed only once, allowing to send'
|
||||||
|
'various mailings in a single campaign to test the effectiveness'
|
||||||
|
'of the mailings.'),
|
||||||
|
'color': fields.integer('Color Index'),
|
||||||
|
# stat fields
|
||||||
|
'total': fields.function(
|
||||||
|
_get_statistics, string='Total',
|
||||||
|
type='integer', multi='_get_statistics'
|
||||||
|
),
|
||||||
|
'scheduled': fields.function(
|
||||||
|
_get_statistics, string='Scheduled',
|
||||||
|
type='integer', multi='_get_statistics'
|
||||||
|
),
|
||||||
|
'failed': fields.function(
|
||||||
|
_get_statistics, string='Failed',
|
||||||
|
type='integer', multi='_get_statistics'
|
||||||
|
),
|
||||||
|
'sent': fields.function(
|
||||||
|
_get_statistics, string='Sent Emails',
|
||||||
|
type='integer', multi='_get_statistics'
|
||||||
|
),
|
||||||
|
'delivered': fields.function(
|
||||||
|
_get_statistics, string='Delivered',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'opened': fields.function(
|
||||||
|
_get_statistics, string='Opened',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'replied': fields.function(
|
||||||
|
_get_statistics, string='Replied',
|
||||||
|
type='integer', multi='_get_statistics'
|
||||||
|
),
|
||||||
|
'bounced': fields.function(
|
||||||
|
_get_statistics, string='Bounced',
|
||||||
|
type='integer', multi='_get_statistics'
|
||||||
|
),
|
||||||
|
'received_ratio': fields.function(
|
||||||
|
_get_statistics, string='Received Ratio',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'opened_ratio': fields.function(
|
||||||
|
_get_statistics, string='Opened Ratio',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'replied_ratio': fields.function(
|
||||||
|
_get_statistics, string='Replied Ratio',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_default_stage_id(self, cr, uid, context=None):
|
||||||
|
stage_ids = self.pool['mail.mass_mailing.stage'].search(cr, uid, [], limit=1, context=context)
|
||||||
|
return stage_ids and stage_ids[0] or False
|
||||||
|
|
||||||
|
_defaults = {
|
||||||
|
'user_id': lambda self, cr, uid, ctx=None: uid,
|
||||||
|
'stage_id': lambda self, *args: self._get_default_stage_id(*args),
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_recipients(self, cr, uid, ids, model=None, context=None):
|
||||||
|
"""Return the recipients of a mailing campaign. This is based on the statistics
|
||||||
|
build for each mailing. """
|
||||||
|
Statistics = self.pool['mail.mail.statistics']
|
||||||
|
res = dict.fromkeys(ids, False)
|
||||||
|
for cid in ids:
|
||||||
|
domain = [('mass_mailing_campaign_id', '=', cid)]
|
||||||
|
if model:
|
||||||
|
domain += [('model', '=', model)]
|
||||||
|
stat_ids = Statistics.search(cr, uid, domain, context=context)
|
||||||
|
res[cid] = set(stat.res_id for stat in Statistics.browse(cr, uid, stat_ids, context=context))
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class MassMailing(osv.Model):
|
||||||
|
""" MassMailing models a wave of emails for a mass mailign campaign.
|
||||||
|
A mass mailing is an occurence of sending emails. """
|
||||||
|
|
||||||
|
_name = 'mail.mass_mailing'
|
||||||
|
_description = 'Mass Mailing'
|
||||||
|
# number of periods for tracking mail_mail statistics
|
||||||
|
_period_number = 6
|
||||||
|
_order = 'sent_date DESC'
|
||||||
|
|
||||||
|
def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, date_begin, context=None):
|
||||||
|
""" Generic method to generate data for bar chart values using SparklineBarWidget.
|
||||||
|
This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).
|
||||||
|
|
||||||
|
:param obj: the target model (i.e. crm_lead)
|
||||||
|
:param domain: the domain applied to the read_group
|
||||||
|
:param list read_fields: the list of fields to read in the read_group
|
||||||
|
:param str value_field: the field used to compute the value of the bar slice
|
||||||
|
:param str groupby_field: the fields used to group
|
||||||
|
|
||||||
|
:return list section_result: a list of dicts: [
|
||||||
|
{ 'value': (int) bar_column_value,
|
||||||
|
'tootip': (str) bar_column_tooltip,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
date_begin = date_begin.date()
|
||||||
|
section_result = [{'value': 0,
|
||||||
|
'tooltip': (date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y'),
|
||||||
|
} for i in range(0, self._period_number)]
|
||||||
|
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
|
||||||
|
field_col_info = obj._all_columns.get(groupby_field.split(':')[0])
|
||||||
|
pattern = tools.DEFAULT_SERVER_DATE_FORMAT if field_col_info.column._type == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
|
||||||
|
for group in group_obj:
|
||||||
|
group_begin_date = datetime.strptime(group['__domain'][0][2], pattern).date()
|
||||||
|
timedelta = relativedelta.relativedelta(group_begin_date, date_begin)
|
||||||
|
section_result[timedelta.days] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field)}
|
||||||
|
return section_result
|
||||||
|
|
||||||
|
def _get_daily_statistics(self, cr, uid, ids, field_name, arg, context=None):
|
||||||
|
""" Get the daily statistics of the mass mailing. This is done by a grouping
|
||||||
|
on opened and replied fields. Using custom format in context, we obtain
|
||||||
|
results for the next 6 days following the mass mailing date. """
|
||||||
|
obj = self.pool['mail.mail.statistics']
|
||||||
|
res = {}
|
||||||
|
for mailing in self.browse(cr, uid, ids, context=context):
|
||||||
|
res[mailing.id] = {}
|
||||||
|
date = mailing.sent_date if mailing.sent_date else mailing.create_date
|
||||||
|
date_begin = datetime.strptime(date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
|
date_end = date_begin + relativedelta.relativedelta(days=self._period_number - 1)
|
||||||
|
date_begin_str = date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
|
date_end_str = date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
|
domain = [('mass_mailing_id', '=', mailing.id), ('opened', '>=', date_begin_str), ('opened', '<=', date_end_str)]
|
||||||
|
res[mailing.id]['opened_dayly'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['opened'], 'opened_count', 'opened:day', date_begin, context=context))
|
||||||
|
domain = [('mass_mailing_id', '=', mailing.id), ('replied', '>=', date_begin_str), ('replied', '<=', date_end_str)]
|
||||||
|
res[mailing.id]['replied_dayly'] = json.dumps(self.__get_bar_values(cr, uid, obj, domain, ['replied'], 'replied_count', 'replied:day', date_begin, context=context))
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _get_statistics(self, cr, uid, ids, name, arg, context=None):
|
||||||
|
""" Compute statistics of the mass mailing campaign """
|
||||||
|
Statistics = self.pool['mail.mail.statistics']
|
||||||
|
results = dict.fromkeys(ids, False)
|
||||||
|
for mid in ids:
|
||||||
|
stat_ids = Statistics.search(cr, uid, [('mass_mailing_id', '=', mid)], context=context)
|
||||||
|
stats = Statistics.browse(cr, uid, stat_ids, context=context)
|
||||||
|
results[mid] = {
|
||||||
|
'total': len(stats),
|
||||||
|
'failed': len([s for s in stats if not s.scheduled is False and s.sent is False and not s.exception is False]),
|
||||||
|
'scheduled': len([s for s in stats if not s.scheduled is False and s.sent is False and s.exception is False]),
|
||||||
|
'sent': len([s for s in stats if not s.sent is False]),
|
||||||
|
'opened': len([s for s in stats if not s.opened is False]),
|
||||||
|
'replied': len([s for s in stats if not s.replied is False]),
|
||||||
|
'bounced': len([s for s in stats if not s.bounced is False]),
|
||||||
|
}
|
||||||
|
results[mid]['delivered'] = results[mid]['sent'] - results[mid]['bounced']
|
||||||
|
results[mid]['received_ratio'] = 100.0 * results[mid]['delivered'] / (results[mid]['total'] or 1)
|
||||||
|
results[mid]['opened_ratio'] = 100.0 * results[mid]['opened'] / (results[mid]['total'] or 1)
|
||||||
|
results[mid]['replied_ratio'] = 100.0 * results[mid]['replied'] / (results[mid]['total'] or 1)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def _get_mailing_model(self, cr, uid, context=None):
|
||||||
|
res = []
|
||||||
|
for model_name in self.pool:
|
||||||
|
model = self.pool[model_name]
|
||||||
|
if hasattr(model, '_mail_mass_mailing') and getattr(model, '_mail_mass_mailing'):
|
||||||
|
res.append((model._name, getattr(model, '_mail_mass_mailing')))
|
||||||
|
res.append(('mail.mass_mailing.contact', _('Mailing List')))
|
||||||
|
return res
|
||||||
|
|
||||||
|
# indirections for inheritance
|
||||||
|
_mailing_model = lambda self, *args, **kwargs: self._get_mailing_model(*args, **kwargs)
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'name': fields.char('Subject', required=True),
|
||||||
|
'email_from': fields.char('From', required=True),
|
||||||
|
'create_date': fields.datetime('Creation Date'),
|
||||||
|
'sent_date': fields.datetime('Sent Date'),
|
||||||
|
'body_html': fields.html('Body'),
|
||||||
|
'attachment_ids': fields.many2many(
|
||||||
|
'ir.attachment', 'mass_mailing_ir_attachments_rel',
|
||||||
|
'mass_mailing_id', 'attachment_id', 'Attachments'
|
||||||
|
),
|
||||||
|
'mass_mailing_campaign_id': fields.many2one(
|
||||||
|
'mail.mass_mailing.campaign', 'Mass Mailing Campaign',
|
||||||
|
ondelete='set null',
|
||||||
|
),
|
||||||
|
'state': fields.selection(
|
||||||
|
[('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')],
|
||||||
|
string='Status', required=True,
|
||||||
|
),
|
||||||
|
'color': fields.related(
|
||||||
|
'mass_mailing_campaign_id', 'color',
|
||||||
|
type='integer', string='Color Index',
|
||||||
|
),
|
||||||
|
# mailing options
|
||||||
|
'reply_to_mode': fields.selection(
|
||||||
|
[('thread', 'In Document'), ('email', 'Specified Email Address')],
|
||||||
|
string='Reply-To Mode', required=True,
|
||||||
|
),
|
||||||
|
'reply_to': fields.char('Reply To', help='Preferred Reply-To Address'),
|
||||||
|
# recipients
|
||||||
|
'mailing_model': fields.selection(_mailing_model, string='Recipients Model', required=True),
|
||||||
|
'mailing_domain': fields.char('Domain'),
|
||||||
|
'contact_list_ids': fields.many2many(
|
||||||
|
'mail.mass_mailing.list', 'mail_mass_mailing_list_rel',
|
||||||
|
string='Mailing Lists',
|
||||||
|
),
|
||||||
|
'contact_ab_pc': fields.integer(
|
||||||
|
'AB Testing percentage',
|
||||||
|
help='Percentage of the contacts that will be mailed. Recipients will be taken randomly.'
|
||||||
|
),
|
||||||
|
# statistics data
|
||||||
|
'statistics_ids': fields.one2many(
|
||||||
|
'mail.mail.statistics', 'mass_mailing_id',
|
||||||
|
'Emails Statistics',
|
||||||
|
),
|
||||||
|
'total': fields.function(
|
||||||
|
_get_statistics, string='Total',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'scheduled': fields.function(
|
||||||
|
_get_statistics, string='Scheduled',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'failed': fields.function(
|
||||||
|
_get_statistics, string='Failed',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'sent': fields.function(
|
||||||
|
_get_statistics, string='Sent',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'delivered': fields.function(
|
||||||
|
_get_statistics, string='Delivered',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'opened': fields.function(
|
||||||
|
_get_statistics, string='Opened',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'replied': fields.function(
|
||||||
|
_get_statistics, string='Replied',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'bounced': fields.function(
|
||||||
|
_get_statistics, string='Bounced',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'received_ratio': fields.function(
|
||||||
|
_get_statistics, string='Received Ratio',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'opened_ratio': fields.function(
|
||||||
|
_get_statistics, string='Opened Ratio',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
'replied_ratio': fields.function(
|
||||||
|
_get_statistics, string='Replied Ratio',
|
||||||
|
type='integer', multi='_get_statistics',
|
||||||
|
),
|
||||||
|
# dayly ratio
|
||||||
|
'opened_dayly': fields.function(
|
||||||
|
_get_daily_statistics, string='Opened',
|
||||||
|
type='char', multi='_get_daily_statistics',
|
||||||
|
oldname='opened_monthly',
|
||||||
|
),
|
||||||
|
'replied_dayly': fields.function(
|
||||||
|
_get_daily_statistics, string='Replied',
|
||||||
|
type='char', multi='_get_daily_statistics',
|
||||||
|
oldname='replied_monthly',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def default_get(self, cr, uid, fields, context=None):
|
||||||
|
res = super(MassMailing, self).default_get(cr, uid, fields, context=context)
|
||||||
|
if 'reply_to_mode' in fields and not 'reply_to_mode' in res and res.get('mailing_model'):
|
||||||
|
if res['mailing_model'] in ['res.partner', 'mail.mass_mailing.contact']:
|
||||||
|
res['reply_to_mode'] = 'email'
|
||||||
|
else:
|
||||||
|
res['reply_to_mode'] = 'thread'
|
||||||
|
return res
|
||||||
|
|
||||||
|
_defaults = {
|
||||||
|
'state': 'draft',
|
||||||
|
'email_from': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx),
|
||||||
|
'reply_to': lambda self, cr, uid, ctx=None: self.pool['mail.message']._get_default_from(cr, uid, context=ctx),
|
||||||
|
'mailing_model': 'mail.mass_mailing.contact',
|
||||||
|
'contact_ab_pc': 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
#------------------------------------------------------
|
||||||
|
# Technical stuff
|
||||||
|
#------------------------------------------------------
|
||||||
|
|
||||||
|
def copy_data(self, cr, uid, id, default=None, context=None):
|
||||||
|
if default is None:
|
||||||
|
default = {}
|
||||||
|
mailing = self.browse(cr, uid, id, context=context)
|
||||||
|
default.update({
|
||||||
|
'state': 'draft',
|
||||||
|
'statistics_ids': [],
|
||||||
|
'name': _('%s (duplicate)') % mailing.name,
|
||||||
|
'sent_date': False,
|
||||||
|
})
|
||||||
|
return super(MassMailing, self).copy_data(cr, uid, id, default, context=context)
|
||||||
|
|
||||||
|
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False, lazy=True):
|
||||||
|
""" Override read_group to always display all states. """
|
||||||
|
if groupby and groupby[0] == "state":
|
||||||
|
# Default result structure
|
||||||
|
# states = self._get_state_list(cr, uid, context=context)
|
||||||
|
states = [('draft', 'Draft'), ('test', 'Tested'), ('done', 'Sent')]
|
||||||
|
read_group_all_states = [{
|
||||||
|
'__context': {'group_by': groupby[1:]},
|
||||||
|
'__domain': domain + [('state', '=', state_value)],
|
||||||
|
'state': state_value,
|
||||||
|
'state_count': 0,
|
||||||
|
} for state_value, state_name in states]
|
||||||
|
# Get standard results
|
||||||
|
read_group_res = super(MassMailing, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
|
||||||
|
# Update standard results with default results
|
||||||
|
result = []
|
||||||
|
for state_value, state_name in states:
|
||||||
|
res = filter(lambda x: x['state'] == state_value, read_group_res)
|
||||||
|
if not res:
|
||||||
|
res = filter(lambda x: x['state'] == state_value, read_group_all_states)
|
||||||
|
res[0]['state'] = [state_value, state_name]
|
||||||
|
result.append(res[0])
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return super(MassMailing, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
|
||||||
|
|
||||||
|
#------------------------------------------------------
|
||||||
|
# Views & Actions
|
||||||
|
#------------------------------------------------------
|
||||||
|
|
||||||
|
def on_change_model_and_list(self, cr, uid, ids, mailing_model, list_ids, context=None):
|
||||||
|
value = {}
|
||||||
|
if mailing_model == 'mail.mass_mailing.contact':
|
||||||
|
list_ids = map(lambda item: item if isinstance(item, (int, long)) else [lid for lid in item[2]], list_ids)
|
||||||
|
if list_ids:
|
||||||
|
value['mailing_domain'] = "[('list_id', 'in', %s)]" % list_ids
|
||||||
|
else:
|
||||||
|
value['mailing_domain'] = "[('list_id', '=', False)]"
|
||||||
|
else:
|
||||||
|
value['mailing_domain'] = False
|
||||||
|
return {'value': value}
|
||||||
|
|
||||||
|
def action_duplicate(self, cr, uid, ids, context=None):
|
||||||
|
copy_id = None
|
||||||
|
for mid in ids:
|
||||||
|
copy_id = self.copy(cr, uid, mid, context=context)
|
||||||
|
if copy_id:
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'view_type': 'form',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'res_model': 'mail.mass_mailing',
|
||||||
|
'res_id': copy_id,
|
||||||
|
'context': context,
|
||||||
|
}
|
||||||
|
return False
|
||||||
|
|
||||||
|
def action_test_mailing(self, cr, uid, ids, context=None):
|
||||||
|
ctx = dict(context, default_mass_mailing_id=ids[0])
|
||||||
|
return {
|
||||||
|
'name': _('Test Mailing'),
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'res_model': 'mail.mass_mailing.test',
|
||||||
|
'target': 'new',
|
||||||
|
'context': ctx,
|
||||||
|
}
|
||||||
|
|
||||||
|
def action_edit_html(self, cr, uid, ids, context=None):
|
||||||
|
if not len(ids) == 1:
|
||||||
|
raise ValueError('One and only one ID allowed for this action')
|
||||||
|
mail = self.browse(cr, uid, ids[0], context=context)
|
||||||
|
url = '/website_mail/email_designer?model=mail.mass_mailing&res_id=%d&template_model=%s&enable_editor=1' % (ids[0], mail.mailing_model)
|
||||||
|
return {
|
||||||
|
'name': _('Open with Visual Editor'),
|
||||||
|
'type': 'ir.actions.act_url',
|
||||||
|
'url': url,
|
||||||
|
'target': 'self',
|
||||||
|
}
|
||||||
|
|
||||||
|
#------------------------------------------------------
|
||||||
|
# Email Sending
|
||||||
|
#------------------------------------------------------
|
||||||
|
|
||||||
|
def get_recipients(self, cr, uid, mailing, context=None):
|
||||||
|
domain = eval(mailing.mailing_domain)
|
||||||
|
res_ids = self.pool[mailing.mailing_model].search(cr, uid, domain, context=context)
|
||||||
|
|
||||||
|
# randomly choose a fragment
|
||||||
|
if mailing.contact_ab_pc < 100:
|
||||||
|
contact_nbr = self.pool[mailing.mailing_model].search(cr, uid, domain, count=True, context=context)
|
||||||
|
topick = int(contact_nbr / 100.0 * mailing.contact_ab_pc)
|
||||||
|
if mailing.mass_mailing_campaign_id and mailing.mass_mailing_campaign_id.unique_ab_testing:
|
||||||
|
already_mailed = self.pool['mail.mass_mailing.campaign'].get_recipients(cr, uid, [mailing.mass_mailing_campaign_id.id], context=context)[mailing.mass_mailing_campaign_id.id]
|
||||||
|
else:
|
||||||
|
already_mailed = set([])
|
||||||
|
remaining = set(res_ids).difference(already_mailed)
|
||||||
|
if topick > len(remaining):
|
||||||
|
topick = len(remaining)
|
||||||
|
res_ids = random.sample(remaining, topick)
|
||||||
|
return res_ids
|
||||||
|
|
||||||
|
def send_mail(self, cr, uid, ids, context=None):
|
||||||
|
author_id = self.pool['res.users'].browse(cr, uid, uid, context=context).partner_id.id
|
||||||
|
for mailing in self.browse(cr, uid, ids, context=context):
|
||||||
|
# instantiate an email composer + send emails
|
||||||
|
res_ids = self.get_recipients(cr, uid, mailing, context=context)
|
||||||
|
comp_ctx = dict(context, active_ids=res_ids)
|
||||||
|
composer_values = {
|
||||||
|
'author_id': author_id,
|
||||||
|
'body': mailing.body_html,
|
||||||
|
'subject': mailing.name,
|
||||||
|
'model': mailing.mailing_model,
|
||||||
|
'email_from': mailing.email_from,
|
||||||
|
'record_name': False,
|
||||||
|
'composition_mode': 'mass_mail',
|
||||||
|
'mass_mailing_id': mailing.id,
|
||||||
|
'mailing_list_ids': [(4, l.id) for l in mailing.contact_list_ids],
|
||||||
|
}
|
||||||
|
if mailing.reply_to_mode == 'email':
|
||||||
|
composer_values['reply_to'] = mailing.reply_to
|
||||||
|
composer_id = self.pool['mail.compose.message'].create(cr, uid, composer_values, context=comp_ctx)
|
||||||
|
self.pool['mail.compose.message'].send_mail(cr, uid, [composer_id], context=comp_ctx)
|
||||||
|
self.write(cr, uid, [mailing.id], {'sent_date': fields.datetime.now(), 'state': 'done'}, context=context)
|
||||||
|
return True
|
|
@ -0,0 +1,109 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# Copyright (C) 2013-today OpenERP SA (<http://www.openerp.com>)
|
||||||
|
#
|
||||||
|
# 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 datetime import datetime
|
||||||
|
from dateutil import relativedelta
|
||||||
|
import random
|
||||||
|
try:
|
||||||
|
import simplejson as json
|
||||||
|
except ImportError:
|
||||||
|
import json
|
||||||
|
import urllib
|
||||||
|
import urlparse
|
||||||
|
|
||||||
|
from openerp import tools
|
||||||
|
from openerp.exceptions import Warning
|
||||||
|
from openerp.tools.safe_eval import safe_eval as eval
|
||||||
|
from openerp.tools.translate import _
|
||||||
|
from openerp.osv import osv, fields
|
||||||
|
|
||||||
|
|
||||||
|
class MailMailStats(osv.Model):
|
||||||
|
""" MailMailStats models the statistics collected about emails. Those statistics
|
||||||
|
are stored in a separated model and table to avoid bloating the mail_mail table
|
||||||
|
with statistics values. This also allows to delete emails send with mass mailing
|
||||||
|
without loosing the statistics about them. """
|
||||||
|
|
||||||
|
_name = 'mail.mail.statistics'
|
||||||
|
_description = 'Email Statistics'
|
||||||
|
_rec_name = 'message_id'
|
||||||
|
_order = 'message_id'
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'mail_mail_id': fields.integer(
|
||||||
|
'Mail ID',
|
||||||
|
help='ID of the related mail_mail. This field is an integer field because'
|
||||||
|
'the related mail_mail can be deleted separately from its statistics.'
|
||||||
|
),
|
||||||
|
'message_id': fields.char('Message-ID'),
|
||||||
|
'model': fields.char('Document model'),
|
||||||
|
'res_id': fields.integer('Document ID'),
|
||||||
|
# campaign / wave data
|
||||||
|
'mass_mailing_id': fields.many2one(
|
||||||
|
'mail.mass_mailing', 'Mass Mailing',
|
||||||
|
ondelete='set null',
|
||||||
|
),
|
||||||
|
'mass_mailing_campaign_id': fields.related(
|
||||||
|
'mass_mailing_id', 'mass_mailing_campaign_id',
|
||||||
|
type='many2one', ondelete='set null',
|
||||||
|
relation='mail.mass_mailing.campaign',
|
||||||
|
string='Mass Mailing Campaign',
|
||||||
|
store=True, readonly=True,
|
||||||
|
),
|
||||||
|
# Bounce and tracking
|
||||||
|
'scheduled': fields.datetime('Scheduled', help='Date when the email has been created'),
|
||||||
|
'sent': fields.datetime('Sent', help='Date when the email has been sent'),
|
||||||
|
'exception': fields.datetime('Exception', help='Date of technical error leading to the email not being sent'),
|
||||||
|
'opened': fields.datetime('Opened', help='Date when the email has been opened the first time'),
|
||||||
|
'replied': fields.datetime('Replied', help='Date when this email has been replied for the first time.'),
|
||||||
|
'bounced': fields.datetime('Bounced', help='Date when this email has bounced.'),
|
||||||
|
}
|
||||||
|
|
||||||
|
_defaults = {
|
||||||
|
'scheduled': fields.datetime.now,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_ids(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, domain=None, context=None):
|
||||||
|
if not ids and mail_mail_ids:
|
||||||
|
base_domain = [('mail_mail_id', 'in', mail_mail_ids)]
|
||||||
|
elif not ids and mail_message_ids:
|
||||||
|
base_domain = [('message_id', 'in', mail_message_ids)]
|
||||||
|
else:
|
||||||
|
base_domain = [('id', 'in', ids or [])]
|
||||||
|
if domain:
|
||||||
|
base_domain = ['&'] + domain + base_domain
|
||||||
|
return self.search(cr, uid, base_domain, context=context)
|
||||||
|
|
||||||
|
def set_opened(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
||||||
|
stat_ids = self._get_ids(cr, uid, ids, mail_mail_ids, mail_message_ids, [('opened', '=', False)], context)
|
||||||
|
self.write(cr, uid, stat_ids, {'opened': fields.datetime.now()}, context=context)
|
||||||
|
return stat_ids
|
||||||
|
|
||||||
|
def set_replied(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
||||||
|
stat_ids = self._get_ids(cr, uid, ids, mail_mail_ids, mail_message_ids, [('replied', '=', False)], context)
|
||||||
|
self.write(cr, uid, stat_ids, {'replied': fields.datetime.now()}, context=context)
|
||||||
|
return stat_ids
|
||||||
|
|
||||||
|
def set_bounced(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
||||||
|
stat_ids = self._get_ids(cr, uid, ids, mail_mail_ids, mail_message_ids, [('bounced', '=', False)], context)
|
||||||
|
self.write(cr, uid, stat_ids, {'bounced': fields.datetime.now()}, context=context)
|
||||||
|
return stat_ids
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from openerp.osv import fields, osv
|
||||||
|
|
||||||
|
|
||||||
|
class MassMailingConfiguration(osv.TransientModel):
|
||||||
|
_name = 'marketing.config.settings'
|
||||||
|
_inherit = 'marketing.config.settings'
|
||||||
|
|
||||||
|
_columns = {
|
||||||
|
'group_mass_mailing_campaign': fields.boolean(
|
||||||
|
'Manage Mass Mailing using Campaign',
|
||||||
|
implied_group='mass_mailing.group_mass_mailing_campaign',
|
||||||
|
help="""Manage mass mailign using Campaigns"""),
|
||||||
|
}
|
|
@ -1,4 +1,8 @@
|
||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_mass_mailing_category,mail.mass_mailing.category,model_mail_mass_mailing_category,base.group_user,1,1,1,1
|
||||||
|
access_mass_mailing_contact,mail.mass_mailing.contact,model_mail_mass_mailing_contact,base.group_user,1,1,1,1
|
||||||
|
access_mass_mailing_list,mail.mass_mailing.list,model_mail_mass_mailing_list,base.group_user,1,1,1,1
|
||||||
|
access_mass_mailing_stage,mail.mass_mailing.stage,model_mail_mass_mailing_stage,base.group_user,1,1,1,1
|
||||||
access_mass_mailing_campaign,mail.mass_mailing.campaign,model_mail_mass_mailing_campaign,base.group_user,1,1,1,0
|
access_mass_mailing_campaign,mail.mass_mailing.campaign,model_mail_mass_mailing_campaign,base.group_user,1,1,1,0
|
||||||
access_mass_mailing_campaign_system,mail.mass_mailing.campaign.system,model_mail_mass_mailing_campaign,base.group_system,1,1,1,1
|
access_mass_mailing_campaign_system,mail.mass_mailing.campaign.system,model_mail_mass_mailing_campaign,base.group_system,1,1,1,1
|
||||||
access_mass_mailing,mail.mass_mailing,model_mail_mass_mailing,base.group_user,1,1,1,0
|
access_mass_mailing,mail.mass_mailing,model_mail_mass_mailing,base.group_user,1,1,1,0
|
||||||
|
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
.openerp .oe_kanban_email_template {
|
||||||
|
width: 360px;
|
||||||
|
min-height: 270px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban_html_preview {
|
||||||
|
width: 600px;
|
||||||
|
height: 600px;
|
||||||
|
-webkit-transform: scale(.50);
|
||||||
|
-ms-transform: scale(.50);
|
||||||
|
transform: scale(.50);
|
||||||
|
-webkit-transform-origin: 0 0;
|
||||||
|
-ms-transform-origin: 0 0;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
margin: 0 0px -300px 0;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
|
@ -1,61 +1,13 @@
|
||||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing.oe_kanban_mass_mailing_campaign {
|
.openerp .oe_kanban_view .oe_kanban_mass_mailing_campaign {
|
||||||
/* Customize to manage content */
|
width: 280px;
|
||||||
width: 552px;
|
min-height: 141px;
|
||||||
min-height: 278px !important;
|
|
||||||
/* End of customize */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing.oe_kanban_mass_mailing_segment {
|
.openerp .oe_kanban_view .oe_kanban_mass_mailing_campaign .oe_kanban_header_right {
|
||||||
/* Customize to manage content */
|
float: right;
|
||||||
width: 282px;
|
|
||||||
min-height: 246px !important;
|
|
||||||
/* End of customize */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_mail_stats {
|
.openerp .oe_kanban_view .oe_kanban_mass_mailing {
|
||||||
width: 122px; /* Manage space in between stats */
|
width: 280px;
|
||||||
display: inline-block;
|
min-height: 141px;
|
||||||
margin: 2px 5px 0px 5px;
|
|
||||||
text-align: center;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.16);
|
|
||||||
-webkit-border-radius: 2px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background-color: #FFFFFF;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_mail_result {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_gauge {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_kanban_content div.oe_sparkline_container {
|
|
||||||
height: 60px;
|
|
||||||
width: 120px;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 8px 5px 0px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_sparkline_bar_title {
|
|
||||||
text-align: center;
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_sparkline_bar {
|
|
||||||
width: 100px;
|
|
||||||
height: 60px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Campaign related CSS
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Segment related CSS
|
|
||||||
*/
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
openerp.mass_mailing = function(openerp) {
|
openerp.mass_mailing = function (instance) {
|
||||||
|
var _t = instance.web._t;
|
||||||
|
|
||||||
openerp.web_kanban.KanbanRecord.include({
|
openerp.web_kanban.KanbanRecord.include({
|
||||||
on_card_clicked: function (event) {
|
on_card_clicked: function (event) {
|
||||||
if (this.view.dataset.model === 'mail.mass_mailing.campaign') {
|
if (this.view.dataset.model === 'mail.mass_mailing.campaign') {
|
||||||
this.$('.oe_mass_mailings a').first().click();
|
this.$('.oe_mailings').click();
|
||||||
} else {
|
} else {
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- Email Templates -->
|
||||||
|
<record model="ir.ui.view" id="email_template_form_minimal">
|
||||||
|
<field name="name">email.template.form.minimal</field>
|
||||||
|
<field name="model">email.template</field>
|
||||||
|
<field name="priority">32</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Templates" version="7.0">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="name" required="True"/>
|
||||||
|
<field name="model_id" required="1" options="{'no_open': True, 'no_create': True}"
|
||||||
|
on_change="onchange_model_id(model_id)"
|
||||||
|
domain="[('model', 'in', ['res.partner', 'mail.mass_mailing.contact'])]"/>
|
||||||
|
<field name="model" invisible="True"/>
|
||||||
|
<field name="use_default_to" invisible="1"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<div class="oe_right oe_button_box" name="buttons">
|
||||||
|
<button name="%(email_template.wizard_email_template_preview)d" string="Preview"
|
||||||
|
type="action" target="new"
|
||||||
|
context="{'template_id':active_id}"/>
|
||||||
|
<br />
|
||||||
|
<!-- <field name="website_link" widget='html' radonly='1'
|
||||||
|
style='margin: 0px; padding: 0px;'/> -->
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page string="Body">
|
||||||
|
<field name="body_html" nolabel="1"/>
|
||||||
|
<field name="attachment_ids" widget="many2many_binary"/>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_email_template_kanban">
|
||||||
|
<field name="name">email.template.kanban</field>
|
||||||
|
<field name="model">email.template</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<kanban>
|
||||||
|
<field name="body_html"/>
|
||||||
|
<templates>
|
||||||
|
<t t-name="kanban-box">
|
||||||
|
<div t-attf-class="oe_kanban_card oe_kanban_global_click oe_kanban_email_template">
|
||||||
|
<div class="oe_dropdown_toggle oe_dropdown_kanban">
|
||||||
|
<span class="oe_e">i</span>
|
||||||
|
<ul class="oe_dropdown_menu">
|
||||||
|
<t t-if="widget.view.is_action_enabled('edit')">
|
||||||
|
<li><a type="edit">Edit</a></li>
|
||||||
|
</t>
|
||||||
|
<t t-if="widget.view.is_action_enabled('delete')">
|
||||||
|
<li><a type="delete">Delete</a></li>
|
||||||
|
</t>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="oe_kanban_content">
|
||||||
|
<h3>
|
||||||
|
<field name="name"/>
|
||||||
|
</h3>
|
||||||
|
<div class="kanban_html_preview">
|
||||||
|
<t t-raw="record.body_html.raw_value"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="oe_clear"></div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
|
</kanban>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.actions.act_window" id="action_email_template_marketing">
|
||||||
|
<field name="name">Templates</field>
|
||||||
|
<field name="res_model">email.template</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">kanban,tree,form</field>
|
||||||
|
<field name="context">{
|
||||||
|
'form_view_ref': 'mass_mailing.email_template_form_minimal',
|
||||||
|
'default_use_default_to': True,
|
||||||
|
}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Add Templates in Marketing / Mass mailing menu -->
|
||||||
|
<menuitem name="Mail Templates" id="menu_email_template"
|
||||||
|
parent="mass_mailing_campaign" sequence="3"
|
||||||
|
action="action_email_template_marketing"/>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
|
@ -0,0 +1,626 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- Marketing / Mass Mailing -->
|
||||||
|
<menuitem name="Mass Mailing" id="mass_mailing_campaign"
|
||||||
|
parent="base.marketing_menu" sequence="1"/>
|
||||||
|
<!-- Marketing / Mailing Lists -->
|
||||||
|
<menuitem name="Mailing Lists" id="mass_mailing_list"
|
||||||
|
parent="base.marketing_menu" sequence="2"/>
|
||||||
|
<!-- Marketing / Configuration -->
|
||||||
|
<menuitem name="Configuration" id="marketing_configuration"
|
||||||
|
parent="base.marketing_menu" sequence="99"/>
|
||||||
|
|
||||||
|
<!-- MASS MAILING CONTACT -->
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_search">
|
||||||
|
<field name="name">mail.mass_mailing.contact.search</field>
|
||||||
|
<field name="model">mail.mass_mailing.contact</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Mailing Lists Subscribers">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="email"/>
|
||||||
|
<field name="list_id"/>
|
||||||
|
<separator/>
|
||||||
|
<filter string="Exclude Opt Out" name="not_opt_out" domain="[('opt_out', '=', False)]"/>
|
||||||
|
<group expand="0" string="Group By...">
|
||||||
|
<filter string="Creation Date" name="group_create_date"
|
||||||
|
context="{'group_by': 'create_date'}"/>
|
||||||
|
<filter string="Mailing Lists" name="group_list_id"
|
||||||
|
context="{'group_by': 'list_id'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_contact_tree">
|
||||||
|
<field name="name">mail.mass_mailing.contact.tree</field>
|
||||||
|
<field name="model">mail.mass_mailing.contact</field>
|
||||||
|
<field name="priority">10</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Mailing Lists Subscribers" editable="top">
|
||||||
|
<field name="email"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="list_id"/>
|
||||||
|
<field name="opt_out"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.actions.act_window" id="action_view_mass_mailing_contacts">
|
||||||
|
<field name="name">Mailing List Subscribers</field>
|
||||||
|
<field name="res_model">mail.mass_mailing.contact</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree</field>
|
||||||
|
<field name="context">{'search_default_not_opt_out': 1}</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.actions.act_window" id="action_view_mass_mailing_contacts_from_list">
|
||||||
|
<field name="name">Recipients</field>
|
||||||
|
<field name="res_model">mail.mass_mailing.contact</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree</field>
|
||||||
|
<field name="context">{'search_default_list_id': active_id, 'search_default_not_opt_out': 1}</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="oe_view_nocontent_create">
|
||||||
|
Click to create a recipient.
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem name="Contacts" id="menu_email_mass_mailing_contacts"
|
||||||
|
parent="mass_mailing_list" sequence="50"
|
||||||
|
action="action_view_mass_mailing_contacts"/>
|
||||||
|
|
||||||
|
<!-- MASS MAILING LIST -->
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_list_search">
|
||||||
|
<field name="name">mail.mass_mailing.list.search</field>
|
||||||
|
<field name="model">mail.mass_mailing.list</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Mailing Lists">
|
||||||
|
<field name="name"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_list_tree">
|
||||||
|
<field name="name">mail.mass_mailing.list.tree</field>
|
||||||
|
<field name="model">mail.mass_mailing.list</field>
|
||||||
|
<field name="priority">10</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Mailing Lists">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="contact_nbr"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_list_form">
|
||||||
|
<field name="name">mail.mass_mailing.list.form</field>
|
||||||
|
<field name="model">mail.mass_mailing.list</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Contact List" version="7.0">
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_right oe_button_box" name="buttons">
|
||||||
|
<button name="%(mass_mailing.action_view_mass_mailing_contacts_from_list)d"
|
||||||
|
type="action" icon="fa-user" class="oe_stat_button pull-right">
|
||||||
|
<field name="contact_nbr" string="Recipients" widget="statinfo"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="oe_title">
|
||||||
|
<label for="name" class="oe_edit_only"/>
|
||||||
|
<h1>
|
||||||
|
<field name="name"/>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.actions.act_window" id="action_view_mass_mailing_lists">
|
||||||
|
<field name="name">Contact Lists</field>
|
||||||
|
<field name="res_model">mail.mass_mailing.list</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="oe_view_nocontent_create">
|
||||||
|
Click here to create a new mailing list.
|
||||||
|
</p><p>
|
||||||
|
Mailing lists allows you to to manage customers and
|
||||||
|
contacts easily and to send to mailings in a single click.
|
||||||
|
</p></field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem name="Mailing Lists" id="menu_email_mass_mailing_lists"
|
||||||
|
parent="mass_mailing_list" sequence="40"
|
||||||
|
action="action_view_mass_mailing_lists"/>
|
||||||
|
|
||||||
|
<!-- MASS MAILING !-->
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_search">
|
||||||
|
<field name="name">mail.mass_mailing.search</field>
|
||||||
|
<field name="model">mail.mass_mailing</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Mass Mailings">
|
||||||
|
<field name="name" string="Mailings"/>
|
||||||
|
<field name="mass_mailing_campaign_id"/>
|
||||||
|
<group expand="0" string="Group By...">
|
||||||
|
<filter string="State" name="group_state"
|
||||||
|
context="{'group_by': 'state'}"/>
|
||||||
|
<filter string="Campaign" name="group_mass_mailing_campaign_id"
|
||||||
|
groups="mass_mailing.group_mass_mailing_campaign"
|
||||||
|
context="{'group_by': 'mass_mailing_campaign_id'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_tree">
|
||||||
|
<field name="name">mail.mass_mailing.tree</field>
|
||||||
|
<field name="model">mail.mass_mailing</field>
|
||||||
|
<field name="priority">10</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Mass Mailings">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="sent"/>
|
||||||
|
<field name="delivered"/>
|
||||||
|
<field name="opened"/>
|
||||||
|
<field name="replied"/>
|
||||||
|
<field name="mass_mailing_campaign_id"
|
||||||
|
groups="mass_mailing.group_mass_mailing_campaign"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_form">
|
||||||
|
<field name="name">mail.mass_mailing.form</field>
|
||||||
|
<field name="model">mail.mass_mailing</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Mass Mailing" version="7.0">
|
||||||
|
<header>
|
||||||
|
<button name="action_test_mailing" type="object"
|
||||||
|
class="oe_highlight" string="Test Mailing" states="draft"/>
|
||||||
|
<button name="send_mail" type="object" states="draft,test"
|
||||||
|
class="oe_highlight" string="Send to All"/>
|
||||||
|
<button name="action_test_mailing" type="object" states="test,done"
|
||||||
|
string="Send Test Sample"/>
|
||||||
|
<field name="state" widget="statusbar"/>
|
||||||
|
</header>
|
||||||
|
<div class="oe_form_box_info oe_text_center" attrs="{'invisible': [('scheduled', '=', 0)]}">
|
||||||
|
<p><strong>
|
||||||
|
<field name="scheduled" class="oe_inline"/>
|
||||||
|
emails are in queue and will be sent soon.
|
||||||
|
</strong></p>
|
||||||
|
</div>
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_button_box pull-right" attrs="{'invisible': [('state', 'in', ('draft','test'))]}">
|
||||||
|
<button name="%(action_view_mass_mailing_contacts)d"
|
||||||
|
type="action" class="oe_stat_button">
|
||||||
|
<field name="received_ratio" string="Received" widget="percentpie"/>
|
||||||
|
</button>
|
||||||
|
<button name="%(action_view_mass_mailing_contacts)d"
|
||||||
|
type="action" class="oe_stat_button">
|
||||||
|
<field name="opened_ratio" string="Opened" widget="percentpie"/>
|
||||||
|
</button>
|
||||||
|
<button name="%(action_view_mass_mailing_contacts)d"
|
||||||
|
type="action" class="oe_stat_button">
|
||||||
|
<field name="replied_ratio" string="Replied" widget="percentpie"/>
|
||||||
|
</button>
|
||||||
|
<button name="%(action_view_mass_mailing_contacts)d"
|
||||||
|
type="action" class="oe_stat_button oe_inline">
|
||||||
|
<field name="opened_dayly" string="Opened Daily" widget="barchart"/>
|
||||||
|
</button>
|
||||||
|
<button name="%(action_view_mass_mailing_contacts)d"
|
||||||
|
type="action" class="oe_stat_button oe_inline">
|
||||||
|
<field name="replied_dayly" string="Replied Daily" widget="barchart"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="oe_button_box" attrs="{'invisible': [('total', '=', 0)]}" style="margin-bottom: 32px">
|
||||||
|
<button name="%(action_view_mass_mailing_contacts)d" type="action"
|
||||||
|
icon="fa-envelope-o" class="oe_stat_button">
|
||||||
|
<field name="total" string="Emails" widget="statinfo"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<group>
|
||||||
|
<field name="email_from"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<label for="mailing_model" string="Recipients"/>
|
||||||
|
<div>
|
||||||
|
<field name="mailing_model" widget="radio" style="margin-bottom: 8px"
|
||||||
|
on_change="on_change_model_and_list(mailing_model, contact_list_ids)"/>
|
||||||
|
|
||||||
|
<field name="mailing_domain" widget="char_domain"
|
||||||
|
placeholder="Select recipients"
|
||||||
|
options="{'model_field': 'mailing_model'}"/>
|
||||||
|
|
||||||
|
<div attrs="{'invisible': [('mailing_model', '<>', 'mail.mass_mailing.contact')]}">
|
||||||
|
<label for="contact_list_ids" string="Select mailing lists:" class="oe_edit_only"/>
|
||||||
|
<field name="contact_list_ids" widget="many2many_tags"
|
||||||
|
placeholder="Select mailing lists..." class="oe_inline"
|
||||||
|
on_change="on_change_model_and_list(mailing_model, contact_list_ids)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page string="Mail Body">
|
||||||
|
<button name="action_edit_html" type="object" string="Design Email" class="oe_highlight" states="draft"/>
|
||||||
|
<button name="action_edit_html" type="object" string="Change Email Design" states="test"/>
|
||||||
|
<div attrs="{'invisible' : ['|', '|', ('state', '=', 'done'), ('body_html','!=',False), ('mailing_domain', '=', False)]}" class="oe_view_nocontent oe_clear">
|
||||||
|
<p class="oe_view_nocontent_create oe_edit_only">
|
||||||
|
Click to design your email.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<field name="body_html" readonly="1"/>
|
||||||
|
<field name="attachment_ids" widget="many2many_binary" string="Attach a file"/>
|
||||||
|
</page>
|
||||||
|
<page string="Options">
|
||||||
|
<group>
|
||||||
|
<group string="Mailing">
|
||||||
|
<label for="reply_to"/>
|
||||||
|
<div>
|
||||||
|
<p class="alert alert-danger"
|
||||||
|
attrs="{'invisible': ['|', ('reply_to_mode', '!=', 'thread'), ('mailing_model', 'not in', ['mail.mass_mailing.contact', 'res.partner'])]}">
|
||||||
|
This option is not available for the recipients you selected.
|
||||||
|
Please use a specific reply-to email address.
|
||||||
|
</p>
|
||||||
|
<field name="reply_to_mode" widget="radio"/>
|
||||||
|
<field name="reply_to" style="margin-left: 16px;"
|
||||||
|
attrs="{'required': [('reply_to_mode', '=', 'email')]}"/>
|
||||||
|
</div>
|
||||||
|
<field name="create_date" readonly="1"/>
|
||||||
|
<field name="sent_date" readonly="1"/>
|
||||||
|
</group>
|
||||||
|
<group string="Campaign">
|
||||||
|
<field name="mass_mailing_campaign_id" groups="mass_mailing.group_mass_mailing_campaign"/>
|
||||||
|
<label for="contact_ab_pc" groups="mass_mailing.group_mass_mailing_campaign"/>
|
||||||
|
<div>
|
||||||
|
<field name="contact_ab_pc" class="oe_inline"/> %
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_kanban">
|
||||||
|
<field name="name">mail.mass_mailing.kanban</field>
|
||||||
|
<field name="model">mail.mass_mailing</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<kanban default_group_by='state'>
|
||||||
|
<field name='color'/>
|
||||||
|
<field name='total'/>
|
||||||
|
<templates>
|
||||||
|
<t t-name="kanban-box">
|
||||||
|
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_kanban_mass_mailing">
|
||||||
|
<div class="oe_dropdown_toggle oe_dropdown_kanban">
|
||||||
|
<span class="oe_e">i</span>
|
||||||
|
<ul class="oe_dropdown_menu">
|
||||||
|
<t t-if="widget.view.is_action_enabled('delete')">
|
||||||
|
<li><a type="delete">Delete</a></li>
|
||||||
|
</t>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="oe_kanban_content">
|
||||||
|
<div>
|
||||||
|
<h3><field name="name"/></h3>
|
||||||
|
<h4 style="display: inline;"><field name="mass_mailing_campaign_id" groups="mass_mailing.group_mass_mailing_campaign"/></h4>
|
||||||
|
<t t-if="record.mass_mailing_campaign_id.raw_value" groups="mass_mailing.group_mass_mailing_campaign"> - </t><field name="sent_date"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div style="display: inline-block">
|
||||||
|
<field name="delivered" widget="gauge" style="width:120px; height: 90px;"
|
||||||
|
options="{'max_field': 'total'}"/>
|
||||||
|
</div>
|
||||||
|
<div style="display: inline-block; vertical-align: top;">
|
||||||
|
<strong>Opened</strong> <field name="opened_ratio"/> %<br />
|
||||||
|
<strong>Replied</strong> <field name="replied_ratio"/> %
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="oe_clear"></div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
|
</kanban>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_view_mass_mailings" model="ir.actions.act_window">
|
||||||
|
<field name="name">Mass Mailings</field>
|
||||||
|
<field name="res_model">mail.mass_mailing</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">kanban,tree,form</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="oe_view_nocontent_create">
|
||||||
|
Click here to create a new mailing.
|
||||||
|
</p><p>
|
||||||
|
Mass mailing allows you to to easily design and send mass mailings to your contacts, customers or leads using mailing lists.
|
||||||
|
</p></field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_view_mass_mailings_from_campaign" model="ir.actions.act_window">
|
||||||
|
<field name="name">Mass Mailings</field>
|
||||||
|
<field name="res_model">mail.mass_mailing</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">kanban,tree,form</field>
|
||||||
|
<field name="context">{
|
||||||
|
'search_default_mass_mailing_campaign_id': [active_id],
|
||||||
|
'default_mass_mailing_campaign_id': active_id,
|
||||||
|
}
|
||||||
|
</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="oe_view_nocontent_create">
|
||||||
|
Click here to create a new mailing.
|
||||||
|
</p><p>
|
||||||
|
Mass mailing allows you to to easily design and send mass mailings to your contacts, customers or leads using mailing lists.
|
||||||
|
</p></field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem name="Mass Mailings" id="menu_email_mass_mailings"
|
||||||
|
parent="mass_mailing_campaign" sequence="2"
|
||||||
|
action="action_view_mass_mailings"/>
|
||||||
|
|
||||||
|
<!-- MASS MAILING CAMPAIGN STAGE !-->
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_stage_search">
|
||||||
|
<field name="name">mail.mass_mailing.stage.search</field>
|
||||||
|
<field name="model">mail.mass_mailing.stage</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Mass Mailings">
|
||||||
|
<field name="name"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_stage_tree">
|
||||||
|
<field name="name">mail.mass_mailing.stage.tree</field>
|
||||||
|
<field name="model">mail.mass_mailing.stage</field>
|
||||||
|
<field name="priority">10</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Mass Mailings" editable="top">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="action_view_mass_mailing_stages" model="ir.actions.act_window">
|
||||||
|
<field name="name">Mass Mailing Stages</field>
|
||||||
|
<field name="res_model">mail.mass_mailing.stage</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem name="Campaign Stages" id="menu_view_mass_mailing_stages"
|
||||||
|
parent="marketing_configuration" sequence="1"
|
||||||
|
groups="mass_mailing.group_mass_mailing_campaign"
|
||||||
|
action="action_view_mass_mailing_stages"/>
|
||||||
|
|
||||||
|
<!-- MASS MAILING CAMPAIGNS !-->
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_search">
|
||||||
|
<field name="name">mail.mass_mailing.campaign.search</field>
|
||||||
|
<field name="model">mail.mass_mailing.campaign</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Mass Mailing Campaigns">
|
||||||
|
<field name="name" string="Campaigns"/>
|
||||||
|
<field name="category_ids"/>
|
||||||
|
<field name="user_id"/>
|
||||||
|
<group expand="0" string="Group By...">
|
||||||
|
<filter string="Stage" name="group_stage_id"
|
||||||
|
context="{'group_by': 'stage_id'}"/>
|
||||||
|
<filter string="Responsible" name="group_user_id"
|
||||||
|
context="{'group_by': 'user_id'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_tree">
|
||||||
|
<field name="name">mail.mass_mailing.campaign.tree</field>
|
||||||
|
<field name="model">mail.mass_mailing.campaign</field>
|
||||||
|
<field name="priority">10</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Mass Mailing Campaigns">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="user_id"/>
|
||||||
|
<field name="stage_id"/>
|
||||||
|
<field name="category_ids"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_form">
|
||||||
|
<field name="name">mail.mass_mailing.campaign.form</field>
|
||||||
|
<field name="model">mail.mass_mailing.campaign</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Mass Mailing Campaign" version="7.0">
|
||||||
|
<header>
|
||||||
|
<field name="stage_id" widget="statusbar" clickable="True"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="user_id"/>
|
||||||
|
<field name="category_ids" widget="many2many_tags"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="total" invisible="1"/>
|
||||||
|
<div class="oe_right oe_button_box" name="buttons"
|
||||||
|
attrs="{'invisible': [('total', '=', 0)]}">
|
||||||
|
<button name="%(action_view_mass_mailing_contacts)d"
|
||||||
|
type="action" class="oe_stat_button oe_inline">
|
||||||
|
<field name="received_ratio" widget="percentpie" string="Received"/>
|
||||||
|
</button>
|
||||||
|
<button name="%(action_view_mass_mailing_contacts)d"
|
||||||
|
type="action" class="oe_stat_button oe_inline">
|
||||||
|
<field name="opened_ratio" widget="percentpie" string="Opened"/>
|
||||||
|
</button>
|
||||||
|
<button name="%(action_view_mass_mailing_contacts)d"
|
||||||
|
type="action" class="oe_stat_button oe_inline">
|
||||||
|
<field name="replied_ratio" widget="percentpie" string="Replied"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<strong>Related Mailing(s)</strong>
|
||||||
|
<field name="mass_mailing_ids" readonly="1" string="Related Mailing(s)">
|
||||||
|
<tree>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="sent_date"/>
|
||||||
|
<field name="state"/>
|
||||||
|
<field name="delivered"/>
|
||||||
|
<field name="opened"/>
|
||||||
|
<field name="replied"/>
|
||||||
|
<field name="bounced"/>
|
||||||
|
<button name="action_duplicate" type="object" string="Duplicate"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_kanban">
|
||||||
|
<field name="name">mail.mass_mailing.campaign.kanban</field>
|
||||||
|
<field name="model">mail.mass_mailing.campaign</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<kanban default_group_by='stage_id'>
|
||||||
|
<field name='total'/>
|
||||||
|
<field name='color'/>
|
||||||
|
<field name='user_id'/>
|
||||||
|
<field name='mass_mailing_ids'/>
|
||||||
|
<templates>
|
||||||
|
<t t-name="kanban-box">
|
||||||
|
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_kanban_mass_mailing_campaign">
|
||||||
|
<div class="oe_dropdown_toggle oe_dropdown_kanban">
|
||||||
|
<span class="oe_e">i</span>
|
||||||
|
<ul class="oe_dropdown_menu">
|
||||||
|
<t t-if="widget.view.is_action_enabled('edit')">
|
||||||
|
<li><a type="edit">Settings</a></li>
|
||||||
|
</t>
|
||||||
|
<t t-if="widget.view.is_action_enabled('delete')">
|
||||||
|
<li><a type="delete">Delete</a></li>
|
||||||
|
</t>
|
||||||
|
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="oe_kanban_content">
|
||||||
|
<div>
|
||||||
|
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)"
|
||||||
|
t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar oe_kanban_header_right"/>
|
||||||
|
<h3 style="margin-bottom: 8px;"><field name="name"/></h3>
|
||||||
|
<field name="category_ids"/>
|
||||||
|
<a name="%(action_view_mass_mailings_from_campaign)d" type="action"
|
||||||
|
class="oe_mailings">
|
||||||
|
<h4 style="margin-top: 8px;"><t t-raw="record.mass_mailing_ids.raw_value.length"/> Mailings</h4>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="oe_clear"></div>
|
||||||
|
<div>
|
||||||
|
<div style="display: inline-block">
|
||||||
|
<field name="delivered" widget="gauge" style="width:120px; height: 90px;"
|
||||||
|
options="{'max_field': 'total'}"/>
|
||||||
|
</div>
|
||||||
|
<div style="display: inline-block; vertical-align: top;">
|
||||||
|
<strong>Opened</strong> <field name="opened_ratio"/> %<br />
|
||||||
|
<strong>Replied</strong> <field name="replied_ratio"/> %
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="oe_clear"></div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
|
</kanban>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_view_mass_mailing_campaigns" model="ir.actions.act_window">
|
||||||
|
<field name="name">Mass Mailing Campaigns</field>
|
||||||
|
<field name="res_model">mail.mass_mailing.campaign</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">kanban,tree,form</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="oe_view_nocontent_create">
|
||||||
|
Click to define a new mass mailing campaign.
|
||||||
|
</p><p>
|
||||||
|
Create a campaign to structure mass mailing and get analysis from email status.
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem name="Campaigns" id="menu_email_campaigns"
|
||||||
|
parent="mass_mailing_campaign" sequence="1"
|
||||||
|
action="action_view_mass_mailing_campaigns"
|
||||||
|
groups="mass_mailing.group_mass_mailing_campaign"/>
|
||||||
|
|
||||||
|
<!-- MAIL MAIL STATISTICS !-->
|
||||||
|
<record model="ir.ui.view" id="view_mail_mail_statistics_search">
|
||||||
|
<field name="name">mail.mail.statistics.search</field>
|
||||||
|
<field name="model">mail.mail.statistics</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Mail Statistics">
|
||||||
|
<field name="mail_mail_id"/>
|
||||||
|
<field name="message_id"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mail_statistics_tree">
|
||||||
|
<field name="name">mail.mail.statistics.tree</field>
|
||||||
|
<field name="model">mail.mail.statistics</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string="Mail Statistics">
|
||||||
|
<field name="mail_mail_id"/>
|
||||||
|
<field name="message_id"/>
|
||||||
|
<field name="sent"/>
|
||||||
|
<field name="opened"/>
|
||||||
|
<field name="replied"/>
|
||||||
|
<field name="bounced"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="view_mail_mail_statistics_form">
|
||||||
|
<field name="name">mail.mail.statistics.form</field>
|
||||||
|
<field name="model">mail.mail.statistics</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Mail Statistics" version="7.0">
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="mail_mail_id"/>
|
||||||
|
<field name="message_id"/>
|
||||||
|
<field name="exception"/>
|
||||||
|
<field name="sent"/>
|
||||||
|
<field name="opened"/>
|
||||||
|
<field name="replied"/>
|
||||||
|
<field name="bounced"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="mass_mailing_id"/>
|
||||||
|
<field name="mass_mailing_campaign_id"/>
|
||||||
|
<field name="model"/>
|
||||||
|
<field name="res_id"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_view_mail_mail_statistics" model="ir.actions.act_window">
|
||||||
|
<field name="name">Mail Statistics</field>
|
||||||
|
<field name="res_model">mail.mail.statistics</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Add in Technical/Email -->
|
||||||
|
<menuitem name="Mail Statistics" id="menu_email_statistics"
|
||||||
|
parent="base.menu_email" sequence="50"
|
||||||
|
action="action_view_mail_mail_statistics"/>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue