Browse Source

Merge pull request #2 from iledarn/9.0

9.0 move mail modules from addons-yelizariev
pull/18/head
Ivan Yelizariev 8 years ago
parent
commit
b783ef698b
  1. 2
      README.md
  2. 4
      mail_check_immediately/README.rst
  3. 2
      mail_check_immediately/__init__.py
  4. 18
      mail_check_immediately/__openerp__.py
  5. 9
      mail_check_immediately/doc/changelog.rst
  6. 67
      mail_check_immediately/models.py
  7. BIN
      mail_check_immediately/static/description/icon.png
  8. 47
      mail_check_immediately/static/description/index.html
  9. BIN
      mail_check_immediately/static/description/issue.png
  10. BIN
      mail_check_immediately/static/description/screenshot.png
  11. 55
      mail_check_immediately/static/src/js/main.js
  12. 25
      mail_check_immediately/static/src/xml/main.xml
  13. 9
      mail_check_immediately/views.xml
  14. 4
      mail_delete_odoo_footer/README.rst
  15. 0
      mail_delete_odoo_footer/__init__.py
  16. 12
      mail_delete_odoo_footer/__openerp__.py
  17. BIN
      mail_delete_odoo_footer/static/description/icon.png
  18. 41
      mail_fix_553/README.rst
  19. 1
      mail_fix_553/__init__.py
  20. 11
      mail_fix_553/__openerp__.py
  21. 10
      mail_fix_553/data.xml
  22. 164
      mail_fix_553/mail_fix_553.py
  23. BIN
      mail_fix_553/static/description/icon.png
  24. 6
      mail_fix_empty_body/README.rst
  25. 1
      mail_fix_empty_body/__init__.py
  26. 14
      mail_fix_empty_body/__openerp__.py
  27. 10
      mail_fix_empty_body/models.py
  28. BIN
      mail_fix_empty_body/static/description/icon.png
  29. 65
      mail_fix_empty_body/static/description/index.html
  30. BIN
      mail_fix_empty_body/static/description/receive-false.png
  31. BIN
      mail_fix_empty_body/static/description/receive-ok.png
  32. BIN
      mail_fix_empty_body/static/description/send.png
  33. 1
      mail_fix_header_from/__init__.py
  34. 16
      mail_fix_header_from/__openerp__.py
  35. 4
      mail_fix_header_from/models.py
  36. BIN
      mail_fix_header_from/static/description/icon.png
  37. 8
      mail_move_message/README.rst
  38. 2
      mail_move_message/__init__.py
  39. 20
      mail_move_message/__openerp__.py
  40. 1
      mail_move_message/controllers/__init__.py
  41. 55
      mail_move_message/controllers/main.py
  42. 9
      mail_move_message/data/mail_move_message_data.xml
  43. 26
      mail_move_message/doc/changelog.rst
  44. 173
      mail_move_message/i18n/mail_move_message.pot
  45. 160
      mail_move_message/i18n/sl.po
  46. BIN
      mail_move_message/images/inbox.png
  47. 382
      mail_move_message/mail_move_message_models.py
  48. 122
      mail_move_message/mail_move_message_views.xml
  49. BIN
      mail_move_message/static/description/delete-message.png
  50. BIN
      mail_move_message/static/description/html-message-viewer.png
  51. BIN
      mail_move_message/static/description/html-message-viewer1.png
  52. BIN
      mail_move_message/static/description/html-message.png
  53. BIN
      mail_move_message/static/description/icon.png
  54. BIN
      mail_move_message/static/description/inbox-move.png
  55. BIN
      mail_move_message/static/description/inbox.png
  56. 140
      mail_move_message/static/description/index.html
  57. BIN
      mail_move_message/static/description/record-move-back.png
  58. BIN
      mail_move_message/static/description/record1.png
  59. BIN
      mail_move_message/static/description/record2.png
  60. 13
      mail_move_message/static/src/css/mail_move_message.css
  61. 91
      mail_move_message/static/src/js/mail_move_message.js
  62. 8
      mail_move_message/static/src/xml/mail_move_message_main.xml
  63. 1
      mail_outgoing/__init__.py
  64. 20
      mail_outgoing/__openerp__.py
  65. 22
      mail_outgoing/mail_outgoing_models.py
  66. 34
      mail_outgoing/mail_outgoing_views.xml
  67. 3
      mail_outgoing/security/ir.model.access.csv
  68. 33
      mail_outgoing/security/mail_outgoing.xml
  69. BIN
      mail_outgoing/static/description/icon.png
  70. 4
      mail_partner_lang/README.rst
  71. 1
      mail_partner_lang/__init__.py
  72. 12
      mail_partner_lang/__openerp__.py
  73. 88
      mail_partner_lang/models.py
  74. BIN
      mail_partner_lang/static/description/icon.png
  75. 1
      mail_recovery/__init__.py
  76. 13
      mail_recovery/__openerp__.py
  77. 10
      mail_recovery/data.xml
  78. 25
      mail_recovery/static/src/js/mail_recovery.js
  79. 8
      mail_sent/README.rst
  80. 1
      mail_sent/__init__.py
  81. 15
      mail_sent/__openerp__.py
  82. 9
      mail_sent/doc/changelog.rst
  83. 50
      mail_sent/i18n/mail_sent.pot
  84. 58
      mail_sent/i18n/sl.po
  85. 28
      mail_sent/models.py
  86. BIN
      mail_sent/static/description/icon.png
  87. 47
      mail_sent/static/description/index.html
  88. BIN
      mail_sent/static/description/menu.png
  89. BIN
      mail_sent/static/description/messages.png
  90. 38
      mail_sent/views.xml
  91. 2
      mail_todo_custom/README.rst
  92. 0
      mail_todo_custom/__init__.py
  93. 13
      mail_todo_custom/__openerp__.py
  94. 25
      mail_todo_custom/mail_todo_custom.xml
  95. 44
      mail_todo_custom/static/src/js/mail_todo_custom.js
  96. 4
      mail_wall_custom/README.rst
  97. 1
      mail_wall_custom/__init__.py
  98. 25
      mail_wall_custom/__openerp__.py
  99. 402
      mail_wall_custom/data.xml
  100. 13
      mail_wall_custom/models.py

2
README.md

@ -7,6 +7,8 @@ List of repositories:
* https://github.com/yelizariev/addons-yelizariev
* https://github.com/yelizariev/pos-addons
* https://github.com/yelizariev/mail-addons
* https://github.com/yelizariev/rental-addons
* https://github.com/yelizariev/access-addons
* https://github.com/yelizariev/website-addons
* https://github.com/yelizariev/l10n-addons
* https://github.com/yelizariev/odoo-saas-tools

4
mail_check_immediately/README.rst

@ -0,0 +1,4 @@
Check mail immediately
======================
Description: https://apps.odoo.com/apps/modules/8.0/mail_check_immediately/

2
mail_check_immediately/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
import models

18
mail_check_immediately/__openerp__.py

@ -0,0 +1,18 @@
{
'name' : 'Check mail immediately',
'version' : '1.0.1',
'author' : 'IT-Projects LLC, Ivan Yelizariev',
'license': 'LGPL-3',
'category' : 'Social Network',
'website' : 'https://twitter.com/yelizariev',
'price': 9.00,
'currency': 'EUR',
'depends' : ['base', 'web', 'fetchmail', 'mail'],
'data': [
'views.xml',
],
'qweb': [
"static/src/xml/main.xml",
],
'installable': False
}

9
mail_check_immediately/doc/changelog.rst

@ -0,0 +1,9 @@
.. _changelog:
Changelog
=========
`1.0.1`
-------
- FIX: incorrectly displayed last updated time when multiple threads (--workers)

67
mail_check_immediately/models.py

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
import datetime
from openerp.tools.translate import _
from openerp import tools
from openerp import exceptions
from openerp import models, fields, api
class FetchMailServer(models.Model):
_inherit = 'fetchmail.server'
_name = 'fetchmail.server'
_last_updated = None
run_time = fields.Datetime(string="Launch time")
def _run_time(self):
if not self._last_updated:
self._last_updated = tools.datetime.now()
src_tstamp_str = self._last_updated.strftime(tools.misc.DEFAULT_SERVER_DATETIME_FORMAT)
src_format = tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
dst_format = tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
dst_tz_name = self._context.get('tz') or self.env.user.tz
_now = tools.misc.server_to_local_timestamp(src_tstamp_str, src_format, dst_format, dst_tz_name)
return _now
@api.model
def _fetch_mails(self):
if self._context.get('run_fetchmail_manually'):
# if interval less than 5 seconds
if self._last_updated and (datetime.datetime.now() - self._last_updated) < datetime.timedelta(0, 5):
raise exceptions.Warning(_('Error'), _('Task can be started no earlier than 5 seconds.'))
super(FetchMailServer, self)._fetch_mails()
res = self.env['fetchmail.server'].sudo().with_context(tz=self.env.user.tz).search([('state', '=', 'done')])
if res:
res[0].run_time = self._run_time()
class FetchMailImmediately(models.AbstractModel):
_name = 'fetch_mail.imm'
@api.model
def get_last_update_time(self):
res = self.env['fetchmail.server'].sudo().with_context(tz=self.env.user.tz).search([('state', '=', 'done')])
array = [r.run_time for r in res]
if array:
return array[0]
else:
return None
@api.model
def run_fetchmail_manually(self):
fetchmail_task = self.env.ref('fetchmail.ir_cron_mail_gateway_action')
fetchmail_task_id = fetchmail_task.id
fetchmail_model = self.env['fetchmail.server'].sudo()
fetchmail_task._try_lock()
fetchmail_model.with_context(run_fetchmail_manually=True)._fetch_mails()

BIN
mail_check_immediately/static/description/icon.png

After

Width: 100  |  Height: 100  |  Size: 2.1 KiB

47
mail_check_immediately/static/description/index.html

@ -0,0 +1,47 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Check mail immediately</h2>
<h3 class="oe_slogan">Keep your inbox up to date</h3>
</div>
<div class="oe_span12">
<div class="oe_demo oe_picture oe_screenshot">
<img src="screenshot.png?"/>
</div>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2>Protect your business</h2>
</div>
<div class="oe_span6">
<p class="oe_mt32">
<a href="https://github.com/odoo/odoo/issues/7464">Sometimes</a> odoo mail fetching system doesn't work for really long time. It could be a real problem, if you will not notice it on time. Until this issue is fixed, you can restart odoo every time when you see that last fetching time is more than 5 minutes.
</p>
</div>
<div class="oe_span6">
<div class="oe_picture">
<img src="issue.png?3"/>
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2>Need our service?</h2>
<p class="oe_mt32">Contact us by <a href="mailto:it@it-projects.info">email</a> or fill out <a href="https://www.it-projects.info/page/website.contactus " target="_blank">request form</a></p>
<ul>
<li><a href="mailto:it@it-projects.info">it@it-projects.info <i class="fa fa-envelope-o"></i></a></li>
<li><a href="https://www.it-projects.info/page/website.contactus " target="_blank">
https://www.it-projects.info/page/website.contactus <i class="fa fa-list-alt"></i></a></li>
</ul>
</div>
</div>
</section>

BIN
mail_check_immediately/static/description/issue.png

After

Width: 217  |  Height: 140  |  Size: 8.5 KiB

BIN
mail_check_immediately/static/description/screenshot.png

After

Width: 840  |  Height: 447  |  Size: 88 KiB

55
mail_check_immediately/static/src/js/main.js

@ -0,0 +1,55 @@
openerp.mail_check_immediately = function(instance, local) {
instance.mail.Wall.include({
init: function(){
this._super.apply(this, arguments);
var _this = this;
this.imm_model = new instance.web.Model('fetch_mail.imm');
this.events['click a.oe_fetch_new_mails'] = function(){
_this.run_fetchmail_manually();
}
},
start: function() {
var _this = this;
this._super();
this.get_last_fetched_time();
this.get_time_loop = setInterval(function(){
_this.get_last_fetched_time()
}, 30000);
},
run_fetchmail_manually: function(){
var _this = this;
this.imm_model.call('run_fetchmail_manually', {context: new instance.web.CompoundContext()}).then(function(){
_this.get_last_fetched_time()
})
},
get_last_fetched_time: function(){
var _this = this;
this.imm_model.call('get_last_update_time', {context: new instance.web.CompoundContext()}).then(function(res){
var value;
if (res)
value = $.timeago(res);
value = value || 'undefined';
_this.$el.find('span.oe_view_manager_fetch_mail_imm_field').html(value);
})
},
destroy: function(){
clearInterval(this.get_time_loop);
this._super.apply(this, arguments);
}
});
};

25
mail_check_immediately/static/src/xml/main.xml

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name="fetch_mail_immediately.header">
<tr class="oe_header_row">
<td t-att-colspan="colspan or '3'">
<div class="oe_view_manager_fetch_mail_imm">
<em>
<span>Mails fetched:</span>
<a href="#" class="oe_fetch_new_mails" title="Click to fetch mails now">
<span class="oe_view_manager_fetch_mail_imm_field"></span>
</a>
</em>
</div>
</td>
<td></td>
</tr>
</t>
<t t-extend="mail.wall">
<t t-jquery="tr.oe_header_row_top" t-operation="after">
<t t-call="fetch_mail_immediately.header">
<t t-set="colspan" t-value="2"/>
</t>
</t>
</t>
</templates>

9
mail_check_immediately/views.xml

@ -0,0 +1,9 @@
<openerp>
<data>
<template id="assets_backend_inherited_check_mail" name="Check mail immediately bar" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/mail_check_immediately/static/src/js/main.js"></script>
</xpath>
</template>
</data>
</openerp>

4
mail_delete_odoo_footer/README.rst

@ -0,0 +1,4 @@
Delete Odoo footer in email
===========================
Tested on 8.0 ab7b5d7732a7c222a0aea45bd173742acd47242d

0
mail_delete_odoo_footer/__init__.py

12
mail_delete_odoo_footer/__openerp__.py

@ -0,0 +1,12 @@
{
'name' : 'Delete Odoo footer in email (TODO)',
'version' : '1.0.0',
'author' : 'IT-Projects LLC, Ivan Yelizariev',
'license': 'LGPL-3',
'category' : 'Social Network',
'website' : 'https://yelizariev.github.io',
'depends' : [],
'data':[
],
'installable': False
}

BIN
mail_delete_odoo_footer/static/description/icon.png

After

Width: 100  |  Height: 100  |  Size: 2.1 KiB

41
mail_fix_553/README.rst

@ -0,0 +1,41 @@
Fix mail error 553
==================
Module updates 'FROM' field to portal@MYDOMAIN.COM value in order to fix 553 error on a mail service that checks FROM field.
E.g:
* Customer send email from USER@CUSTOMER.com to info@MYDOMAIN.COM
* odoo accept email and try to send notifcation to related odoo users. E.g to admin@gmail.com.
* By default odoo prepare notification email with parameters as follows:
* FROM: user@CUSTOMER.com
* TO: admin@gmail.com
if you mail service provider, e.g. pdd.yandex.ru, doesn't allow emails with a FROM value differ from ...@MYDOMAIN.COM, then you get 553. This is why you need to update FROM value to portal@MYDOMAIN.COM
Configuration
=============
You can configure default alias at Settings -> System Parameters -> mail.catchall.alias_from
Known issues / Roadmap
======================
The module is consist of redefined send function from mail.mail
model. So it is just copy pasted source code with some
modification. This function is changed very rarely, but sometime it
can happens and the module should be updated. You can check commits
for mail_mail.py here:
https://github.com/odoo/odoo/commits/8.0/addons/mail/mail_mail.py
Tested on Odoo 8.0 d023c079ed86468436f25da613bf486a4a17d625
Status
======
Related issues at odoo's tracker:
* https://github.com/odoo/odoo/issues/5864
* https://github.com/odoo/odoo/issues/3347
Fix: https://github.com/odoo-dev/odoo/commit/a4597fe34fcfa8dae28b156410080346bb33af33

1
mail_fix_553/__init__.py

@ -0,0 +1 @@
import mail_fix_553

11
mail_fix_553/__openerp__.py

@ -0,0 +1,11 @@
{
"name" : "Fix mail error 553",
"version" : "0.3",
"author" : "IT-Projects LLC, Ivan Yelizariev",
'license': 'LGPL-3',
"category" : "Social Network",
"website" : "https://yelizariev.github.io",
"depends" : ["base", "mail"],
"data": ["data.xml"],
'installable': False
}

10
mail_fix_553/data.xml

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<!-- Catchall Email Alias -->
<record id="icp_mail_catchall_alias_from" model="ir.config_parameter">
<field name="key">mail.catchall.alias_from</field>
<field name="value">portal</field>
</record>
</data>
</openerp>

164
mail_fix_553/mail_fix_553.py

@ -0,0 +1,164 @@
# -*- coding: utf-8 -*-
import base64
import logging
from email.utils import formataddr
from urlparse import urljoin
from openerp import api, tools
from openerp import SUPERUSER_ID
from openerp.addons.base.ir.ir_mail_server import MailDeliveryException
from openerp.osv import fields, osv
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
import re
class mail_mail(osv.Model):
_inherit = "mail.mail"
def send(self, cr, uid, ids, auto_commit=False, raise_exception=False, context=None):
# copy-paste from addons/mail/mail_mail.py
""" Sends the selected emails immediately, ignoring their current
state (mails that have already been sent should not be passed
unless they should actually be re-sent).
Emails successfully delivered are marked as 'sent', and those
that fail to be deliver are marked as 'exception', and the
corresponding error mail is output in the server logs.
:param bool auto_commit: whether to force a commit of the mail status
after sending each mail (meant only for scheduler processing);
should never be True during normal transactions (default: False)
:param bool raise_exception: whether to raise an exception if the
email sending process has failed
:return: True
"""
# NEW STUFF
catchall_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.alias_from", context=context)
catchall_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context)
correct_email_from = '@%s>?\s*$'%catchall_domain
default_email_from = '%s@%s' % (catchall_alias, catchall_domain)
context = dict(context or {})
ir_mail_server = self.pool.get('ir.mail_server')
ir_attachment = self.pool['ir.attachment']
for mail in self.browse(cr, SUPERUSER_ID, ids, context=context):
try:
# TDE note: remove me when model_id field is present on mail.message - done here to avoid doing it multiple times in the sub method
if mail.model:
model_id = self.pool['ir.model'].search(cr, SUPERUSER_ID, [('model', '=', mail.model)], context=context)[0]
model = self.pool['ir.model'].browse(cr, SUPERUSER_ID, model_id, context=context)
else:
model = None
if model:
context['model_name'] = model.name
# load attachment binary data with a separate read(), as prefetching all
# `datas` (binary field) could bloat the browse cache, triggerring
# soft/hard mem limits with temporary data.
attachment_ids = [a.id for a in mail.attachment_ids]
attachments = [(a['datas_fname'], base64.b64decode(a['datas']))
for a in ir_attachment.read(cr, SUPERUSER_ID, attachment_ids,
['datas_fname', 'datas'])]
# specific behavior to customize the send email for notified partners
email_list = []
if mail.email_to:
email_list.append(self.send_get_email_dict(cr, uid, mail, context=context))
for partner in mail.recipient_ids:
email_list.append(self.send_get_email_dict(cr, uid, mail, partner=partner, context=context))
# headers
headers = {}
bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context)
catchall_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context)
if bounce_alias and catchall_domain:
if mail.model and mail.res_id:
headers['Return-Path'] = '%s-%d-%s-%d@%s' % (bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain)
else:
headers['Return-Path'] = '%s-%d@%s' % (bounce_alias, mail.id, catchall_domain)
if mail.headers:
try:
headers.update(eval(mail.headers))
except Exception:
pass
# Writing on the mail object may fail (e.g. lock on user) which
# would trigger a rollback *after* actually sending the email.
# To avoid sending twice the same email, provoke the failure earlier
mail.write({'state': 'exception'})
mail_sent = False
# build an RFC2822 email.message.Message object and send it without queuing
res = None
for email in email_list:
# NEW STUFF
email_from = mail.email_from
if re.search(correct_email_from, email_from) is None:
email_from = default_email_from
msg = ir_mail_server.build_email(
email_from=email_from, # NEW STUFF
email_to=email.get('email_to'),
subject=email.get('subject'),
body=email.get('body'),
body_alternative=email.get('body_alternative'),
email_cc=tools.email_split(mail.email_cc),
reply_to=mail.reply_to,
attachments=attachments,
message_id=mail.message_id,
references=mail.references,
object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
subtype='html',
subtype_alternative='plain',
headers=headers)
try:
res = ir_mail_server.send_email(cr, uid, msg,
mail_server_id=mail.mail_server_id.id,
context=context)
except AssertionError as error:
if error.message == ir_mail_server.NO_VALID_RECIPIENT:
# No valid recipient found for this particular
# mail item -> ignore error to avoid blocking
# delivery to next recipients, if any. If this is
# the only recipient, the mail will show as failed.
_logger.warning("Ignoring invalid recipients for mail.mail %s: %s",
mail.message_id, email.get('email_to'))
else:
raise
if res:
mail.write({'state': 'sent', 'message_id': res})
mail_sent = True
# /!\ can't use mail.state here, as mail.refresh() will cause an error
# see revid:odo@openerp.com-20120622152536-42b2s28lvdv3odyr in 6.1
if mail_sent:
_logger.info('Mail with ID %r and Message-Id %r successfully sent', mail.id, mail.message_id)
self._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=mail_sent)
except MemoryError:
# prevent catching transient MemoryErrors, bubble up to notify user or abort cron job
# instead of marking the mail as failed
_logger.exception('MemoryError while processing mail with ID %r and Msg-Id %r. '\
'Consider raising the --limit-memory-hard startup option',
mail.id, mail.message_id)
raise
except Exception as e:
_logger.exception('failed sending mail.mail %s', mail.id)
mail.write({'state': 'exception'})
self._postprocess_sent_message(cr, uid, mail, context=context, mail_sent=False)
if raise_exception:
if isinstance(e, AssertionError):
# get the args of the original error, wrap into a value and throw a MailDeliveryException
# that is an except_orm, with name and value as arguments
value = '. '.join(e.args)
raise MailDeliveryException(_("Mail Delivery Failed"), value)
raise
if auto_commit is True:
cr.commit()
return True

BIN
mail_fix_553/static/description/icon.png

After

Width: 100  |  Height: 100  |  Size: 2.1 KiB

6
mail_fix_empty_body/README.rst

@ -0,0 +1,6 @@
Fix "False" in empty email body
===============================
Description: https://apps.odoo.com/apps/modules/8.0/mail_fix_empty_body/
Tested on Odoo 8.0 ab7b5d7732a7c222a0aea45bd173742acd47242d

1
mail_fix_empty_body/__init__.py

@ -0,0 +1 @@
import models

14
mail_fix_empty_body/__openerp__.py

@ -0,0 +1,14 @@
{
'name' : 'Fix "False" in empty email body',
'version' : '1.0.0',
'author' : 'IT-Projects LLC, Ivan Yelizariev',
'license': 'LGPL-3',
'category': 'Social Network',
'website' : 'https://twitter.com/yelizariev',
'price': 9.00,
'currency': 'EUR',
'depends' : ['mail'],
'data':[
],
'installable': False
}

10
mail_fix_empty_body/models.py

@ -0,0 +1,10 @@
from openerp import api, models, fields, SUPERUSER_ID
class mail_compose_message(models.TransientModel):
_inherit = 'mail.compose.message'
def get_mail_values(self, cr, uid, wizard, res_ids, context=None):
res = super(mail_compose_message, self).get_mail_values(cr, uid, wizard, res_ids, context)
for id, d in res.iteritems():
d['body'] = d.get('body') or ''
return res

BIN
mail_fix_empty_body/static/description/icon.png

After

Width: 100  |  Height: 100  |  Size: 2.1 KiB

65
mail_fix_empty_body/static/description/index.html

@ -0,0 +1,65 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Fix "False" in empty email body</h2>
<h3 class="oe_slogan">Feel free to send emails without body</h3>
</div>
<div class="oe_span8">
<p class="oe_mt32">
Some time you need to send email with empty body. <br/>E.g. to send some file to partner.
</p>
</div>
<div class="oe_span8 oe_right">
<div class="oe_demo oe_picture oe_screenshot">
<img src="send.png?1"/>
</div>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span6">
<div class="oe_demo oe_picture oe_screenshot">
<img src="receive-false.png"/>
</div>
</div>
<div class="oe_span6">
<p class="oe_mt32">
There is a bug in odoo -- it sends "False" if email body if empty.
</p>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span6">
<p class="oe_mt32">
This module just fix the issue.
</p>
</div>
<div class="oe_span6">
<div class="oe_demo oe_picture oe_screenshot">
<img src="receive-ok.png"/>
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2>Need our service?</h2>
<p class="oe_mt32">Contact us by <a href="mailto:it@it-projects.info">email</a> or fill out <a href="https://www.it-projects.info/page/website.contactus " target="_blank">request form</a></p>
<ul>
<li><a href="mailto:it@it-projects.info">it@it-projects.info <i class="fa fa-envelope-o"></i></a></li>
<li><a href="https://www.it-projects.info/page/website.contactus " target="_blank">
https://www.it-projects.info/page/website.contactus <i class="fa fa-list-alt"></i></a></li>
</ul>
</div>
</div>
</section>

BIN
mail_fix_empty_body/static/description/receive-false.png

After

Width: 366  |  Height: 276  |  Size: 18 KiB

BIN
mail_fix_empty_body/static/description/receive-ok.png

After

Width: 350  |  Height: 280  |  Size: 16 KiB

BIN
mail_fix_empty_body/static/description/send.png

After

Width: 581  |  Height: 400  |  Size: 31 KiB

1
mail_fix_header_from/__init__.py

@ -0,0 +1 @@
import models

16
mail_fix_header_from/__openerp__.py

@ -0,0 +1,16 @@
{
"name" : "Fix non-ascii header 'from' (OBSOLETE)",
"version" : "0.3",
"author" : "IT-Projects LLC, Ivan Yelizariev",
'license': 'LGPL-3',
"category" : "Social Network",
"website" : "https://yelizariev.github.io",
"description": """
Obsolete in odoo 8.0 since Sep 10, 2014 https://github.com/odoo/odoo/commit/f2cf6ced17d3477b8858e3a8f955a42cc8a629ff . You can install this module, if you use older version.
""",
"depends" : ["base"],
#"init_xml" : [],
#"update_xml" : [],
#"active": True,
'installable': False
}

4
mail_fix_header_from/models.py

@ -0,0 +1,4 @@
import re
from openerp.addons.base.ir import ir_mail_server
ir_mail_server.name_with_email_pattern = re.compile(r'([^<@>]+)\s*<([^ ,<@]+@[^> ,]+)>')

BIN
mail_fix_header_from/static/description/icon.png

After

Width: 100  |  Height: 100  |  Size: 2.1 KiB

8
mail_move_message/README.rst

@ -0,0 +1,8 @@
Mail relocation
===============
Description: https://www.odoo.com/apps/modules/8.0/mail_move_message/
Further information and discussion: http://yelizariev.github.io/odoo/module/2015/04/10/mail-relocation.html
Tested on Odoo 8.0 d023c079ed86468436f25da613bf486a4a17d625

2
mail_move_message/__init__.py

@ -0,0 +1,2 @@
import controllers
import mail_move_message_models

20
mail_move_message/__openerp__.py

@ -0,0 +1,20 @@
{
'name' : 'Mail relocation',
'version' : '1.0.4',
'author' : 'IT-Projects LLC, Ivan Yelizariev',
'license': 'LGPL-3',
'category' : 'Social Network',
'website' : 'https://twitter.com/yelizariev',
'price': 9.00,
'currency': 'EUR',
'depends' : ['mail', 'web_polymorphic_field'],
'images': ['images/inbox.png'],
'data':[
'mail_move_message_views.xml',
'data/mail_move_message_data.xml',
],
'qweb': [
'static/src/xml/mail_move_message_main.xml',
],
'installable': False
}

1
mail_move_message/controllers/__init__.py

@ -0,0 +1 @@
import main

55
mail_move_message/controllers/main.py

@ -0,0 +1,55 @@
from openerp.addons.web.controllers.main import DataSet
from openerp.tools.translate import _
from openerp import http
from openerp.http import request
class DataSetCustom(DataSet):
def _extend_name(self, model, records):
cr, uid, context = request.cr, request.uid, request.context
Model = request.registry[model]
fields = Model.fields_get(cr, uid, False, context)
contact_field = False
for n, f in fields.iteritems():
if f['type'] == 'many2one' and f['relation'] == 'res.partner':
contact_field = n
break
partner_info = {}
if contact_field:
partner_info = Model.read(cr, uid, [r[0] for r in records], [contact_field], context)
partner_info = dict([(p['id'], p[contact_field]) for p in partner_info])
res = []
for r in records:
if partner_info.get(r[0]):
res.append((r[0], _('%s [%s] ID %s') % (r[1], partner_info.get(r[0])[1], r[0])))
else:
res.append((r[0], _('%s ID %s') % (r[1], r[0])))
return res
@http.route('/web/dataset/call_kw/<model>/name_search', type='json', auth="user")
def name_search(self, model, method, args, kwargs):
context = kwargs.get('context')
if context and context.get('extended_name_with_contact'):
#add order by ID desc
cr, uid = request.cr, request.uid
Model = request.registry[model]
search_args = list(kwargs.get('args') or [])
limit = int(kwargs.get('limit') or 100)
operator = kwargs.get('operator')
name = kwargs.get('name')
if Model._rec_name and (not name == '' and operator == 'ilike'):
search_args += [(Model._rec_name, operator, name)]
ids = Model.search(cr, uid, search_args, limit=limit, order='id desc', context=context)
res = Model.name_get(cr, uid, ids, context)
return self._extend_name(model, res)
return self._call_kw(model, method, args, kwargs)
@http.route('/web/dataset/call_kw/<model>/name_get', type='json', auth="user")
def name_get(self, model, method, args, kwargs):
res = self._call_kw(model, method, args, kwargs)
context = kwargs.get('context')
if context and context.get('extended_name_with_contact'):
res = self._extend_name(model, res)
return res

9
mail_move_message/data/mail_move_message_data.xml

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
<record id="mail_relocation_models" model="ir.config_parameter">
<field name="key">mail_relocation_models</field>
<field name="value">crm.lead,project.task</field>
</record>
</data>
</openerp>

26
mail_move_message/doc/changelog.rst

@ -0,0 +1,26 @@
.. _changelog:
Changelog
=========
`1.0.4`
-------
- FIX: don't allow to relocate message to itself as it cause infinitive loop
- ADD: 'Move Followers' option -- Add followers of current record to a new record.
`1.0.3`
-------
- FIX email_from parsing. There was an error with specific email_from value (e.g. '"name @ example" <name@example.com>')
`1.0.2`
-------
- big improvements in interface
`1.0.1`
-------
- fix bug "some messages are not shown in inbox after relocation"
- improve "Move back" tool

173
mail_move_message/i18n/mail_move_message.pot

@ -0,0 +1,173 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mail_move_message
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-08-11 06:53+0000\n"
"PO-Revision-Date: 2015-08-11 06:53+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: mail_move_message
#: view:mail_move_message.wizard:mail_move_message.view_wizard
msgid "Cancel"
msgstr ""
#. module: mail_move_message
#: field:mail_move_message.wizard,create_uid:0
msgid "Created by"
msgstr ""
#. module: mail_move_message
#: field:mail_move_message.wizard,create_date:0
msgid "Created on"
msgstr ""
#. module: mail_move_message
#: field:mail_move_message.wizard,id:0
msgid "ID"
msgstr ""
#. module: mail_move_message
#: field:mail.message,is_moved:0
msgid "Is moved"
msgstr ""
#. module: mail_move_message
#: field:mail_move_message.wizard,write_uid:0
msgid "Last Updated by"
msgstr ""
#. module: mail_move_message
#: field:mail_move_message.wizard,write_date:0
msgid "Last Updated on"
msgstr ""
#. module: mail_move_message
#: field:mail_move_message.wizard,record_url:0
msgid "Link to record"
msgstr ""
#. module: mail_move_message
#: model:ir.model,name:mail_move_message.model_mail_message
#: view:mail_move_message.wizard:mail_move_message.view_wizard
#: field:mail_move_message.wizard,message_id:0
msgid "Message"
msgstr ""
#. module: mail_move_message
#: view:mail_move_message.wizard:mail_move_message.view_wizard
msgid "Move"
msgstr ""
#. module: mail_move_message
#: help:mail_move_message.wizard,move_back:0
msgid "Move message and submessages to original place"
msgstr ""
#. module: mail_move_message
#: view:mail_move_message.wizard:mail_move_message.view_wizard
msgid "Move Message"
msgstr ""
#. module: mail_move_message
#: field:mail_move_message.wizard,move_back:0
msgid "Move to origin"
msgstr ""
#. module: mail_move_message
#. openerp-web
#: code:addons/mail_move_message/static/src/xml/mail_move_message_main.xml:5
#, python-format
msgid "Move to thread"
msgstr ""
#. module: mail_move_message
#: field:mail.message,moved_by_message_id:0
msgid "Moved by message"
msgstr ""
#. module: mail_move_message
#: field:mail.message,moved_by_user_id:0
msgid "Moved by user"
msgstr ""
#. module: mail_move_message
#: view:mail_move_message.wizard:mail_move_message.view_wizard
msgid "Open message"
msgstr ""
#. module: mail_move_message
#: field:mail.message,moved_from_parent_id:0
msgid "Parent Message (Original)"
msgstr ""
#. module: mail_move_message
#: code:addons/mail_move_message/mail_move_message_models.py:107
#, python-format
msgid "Record"
msgstr ""
#. module: mail_move_message
#: field:mail_move_message.wizard,res_id:0
msgid "Record ID"
msgstr ""
#. module: mail_move_message
#: field:mail_move_message.wizard,model_id:0
msgid "Record type"
msgstr ""
#. module: mail_move_message
#: field:mail.message,moved_from_res_id:0
msgid "Related Document ID (Original)"
msgstr ""
#. module: mail_move_message
#: field:mail.message,moved_from_model:0
msgid "Related Document Model (Original)"
msgstr ""
#. module: mail_move_message
#. openerp-web
#: code:addons/mail_move_message/static/src/js/mail_move_message.js:17
#, python-format
msgid "Relocate Message"
msgstr ""
#. module: mail_move_message
#: field:mail_move_message.wizard,parent_id:0
msgid "Search by name"
msgstr ""
#. module: mail_move_message
#: help:mail.message,moved_by_message_id:0
msgid "Top message, that initate moving this message"
msgstr ""
#. module: mail_move_message
#: view:mail_move_message.wizard:mail_move_message.view_wizard
msgid "You cannot move this message. It was already moved with a message bellow. Open one and apply changes there."
msgstr ""
#. module: mail_move_message
#: help:mail_move_message.wizard,model_id:0
msgid "List available Models is configured at Settings\Technical\Emails\Mail Relocation. Empty for unassigned email"
msgstr ""
#. module: mail_move_message
#: help:mail_move_message.wizard,filter_by_partner:0
msgid "Show only records with the same partner as email author"
msgstr ""
#. module: mail_move_message
#: help:mail_move_message.wizard,move_followers:0
msgid "Add followers of current record to a new record.\nYou must use this option, if new record has restricted access.\nYou can change default value for this option at Settings/System Parameters"
msgstr ""

160
mail_move_message/i18n/sl.po

@ -0,0 +1,160 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mail_move_message
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-08-11 06:53+0000\n"
"PO-Revision-Date: 2015-08-11 08:58+0200\n"
"Last-Translator: Matjaz Mozetic <m.mozetic@matmoz.si>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: \n"
"Language: sl\n"
"X-Generator: Poedit 1.8.2\n"
#. module: mail_move_message
#: view:mail_move_message.wizard:mail_move_message.view_wizard
msgid "Cancel"
msgstr "Preklic"
#. module: mail_move_message
#: field:mail_move_message.wizard,create_uid:0
msgid "Created by"
msgstr "Ustvaril"
#. module: mail_move_message
#: field:mail_move_message.wizard,create_date:0
msgid "Created on"
msgstr "Ustvarjeno"
#. module: mail_move_message
#: field:mail_move_message.wizard,id:0
msgid "ID"
msgstr "ID"
#. module: mail_move_message
#: field:mail.message,is_moved:0
msgid "Is moved"
msgstr "Je premaknjeno"
#. module: mail_move_message
#: field:mail_move_message.wizard,write_uid:0
msgid "Last Updated by"
msgstr "Zadnjič posodobil"
#. module: mail_move_message
#: field:mail_move_message.wizard,write_date:0
msgid "Last Updated on"
msgstr "Zadnjič posodobljeno"
#. module: mail_move_message
#: field:mail_move_message.wizard,record_url:0
msgid "Link to record"
msgstr "Povezava do zapisa"
#. module: mail_move_message
#: model:ir.model,name:mail_move_message.model_mail_message
#: view:mail_move_message.wizard:mail_move_message.view_wizard
#: field:mail_move_message.wizard,message_id:0
msgid "Message"
msgstr "Sporočilo"
#. module: mail_move_message
#: view:mail_move_message.wizard:mail_move_message.view_wizard
msgid "Move"
msgstr "Premik"
#. module: mail_move_message
#: help:mail_move_message.wizard,move_back:0
msgid "Move message and submessages to original place"
msgstr "Premik sporočila in podrejenih sporočil na izvorno mesto"
#. module: mail_move_message
#: view:mail_move_message.wizard:mail_move_message.view_wizard
msgid "Move Message"
msgstr "Premik sporočila"
#. module: mail_move_message
#: field:mail_move_message.wizard,move_back:0
msgid "Move to origin"
msgstr "Premik na izvor"
#. module: mail_move_message
#. openerp-web
#: code:addons/mail_move_message/static/src/xml/mail_move_message_main.xml:5
#, python-format
msgid "Move to thread"
msgstr "Premik v nit"
#. module: mail_move_message
#: field:mail.message,moved_by_message_id:0
msgid "Moved by message"
msgstr "Premaknjeno s sporočilom"
#. module: mail_move_message
#: field:mail.message,moved_by_user_id:0
msgid "Moved by user"
msgstr "Premaknil uporabnik"
#. module: mail_move_message
#: view:mail_move_message.wizard:mail_move_message.view_wizard
msgid "Open message"
msgstr "Odpri sporočilo"
#. module: mail_move_message
#: field:mail.message,moved_from_parent_id:0
msgid "Parent Message (Original)"
msgstr "Nadrejeno sporočilo (original)"
#. module: mail_move_message
#: code:addons/mail_move_message/mail_move_message_models.py:107
#, python-format
msgid "Record"
msgstr "Zapis"
#. module: mail_move_message
#: field:mail_move_message.wizard,res_id:0
msgid "Record ID"
msgstr "ID zapisa"
#. module: mail_move_message
#: field:mail_move_message.wizard,model_id:0
msgid "Record type"
msgstr "Tip zapisa"
#. module: mail_move_message
#: field:mail.message,moved_from_res_id:0
msgid "Related Document ID (Original)"
msgstr "ID povezanega dokumenta (original)"
#. module: mail_move_message
#: field:mail.message,moved_from_model:0
msgid "Related Document Model (Original)"
msgstr "Model povezanega dokumenta (original)"
#. module: mail_move_message
#. openerp-web
#: code:addons/mail_move_message/static/src/js/mail_move_message.js:17
#, python-format
msgid "Relocate Message"
msgstr "Premik sporočila"
#. module: mail_move_message
#: field:mail_move_message.wizard,parent_id:0
msgid "Search by name"
msgstr "Iskanje po nazivu"
#. module: mail_move_message
#: help:mail.message,moved_by_message_id:0
msgid "Top message, that initate moving this message"
msgstr "Zgornje sporočilo, ki je sprožilo premik tega sporočila"
#. module: mail_move_message
#: view:mail_move_message.wizard:mail_move_message.view_wizard
msgid "You cannot move this message. It was already moved with a message bellow. Open one and apply changes there."
msgstr "Tega sporočila ne morete premakniti, ker je bilo že premaknjeno s spodnjim sporočilom. Tam lahko uveljavljate spremembe."

BIN
mail_move_message/images/inbox.png

After

Width: 861  |  Height: 628  |  Size: 128 KiB

382
mail_move_message/mail_move_message_models.py

@ -0,0 +1,382 @@
from lxml import etree
from openerp import api, models, fields, SUPERUSER_ID
from openerp.tools import email_split
from openerp.tools.translate import _
class wizard(models.TransientModel):
_name = 'mail_move_message.wizard'
def _model_selection(self):
selection = []
config_parameters = self.env['ir.config_parameter']
model_names = config_parameters.get_param('mail_relocation_models')
model_names = model_names.split(',') if model_names else []
if 'default_message_id' in self.env.context:
message = self.env['mail.message'].browse(self.env.context['default_message_id'])
if message.model and message.model not in model_names:
model_names.append(message.model)
if message.moved_from_model and message.moved_from_model not in model_names:
model_names.append(message.moved_from_model)
if model_names:
selection = [(m.model, m.display_name) for m in self.env['ir.model'].search([('model', 'in', model_names)])]
return selection
@api.model
def default_get(self, fields_list):
res = super(wizard, self).default_get(fields_list)
model_fields = self.fields_get()
if model_fields['model']['selection']:
res['model'] = model_fields['model']['selection'] and model_fields['model']['selection'][0][0]
if 'message_id' in res:
message = self.env['mail.message'].browse(res['message_id'])
email_from = message.email_from
parts = email_split(email_from.replace(' ',','))
if parts:
email = parts[0]
name = email_from.find(email) != -1 and email_from[:email_from.index(email)].replace('"', '').replace('<', '').strip() or email_from
else:
name, email = email_from
res['message_name_from'] = name
res['message_email_from'] = email
res['partner_id'] = message.author_id.id
if message.author_id and self.env.uid not in [u.id for u in message.author_id.user_ids]:
res['filter_by_partner'] = True
if message.author_id and res.get('model'):
res_id = self.env[res['model']].search([], order='id desc', limit=1)
res['res_id'] = res_id and res_id[0].id
config_parameters = self.env['ir.config_parameter']
res['move_followers'] = config_parameters.get_param('mail_relocation_move_followers')
res['uid'] = self.env.uid
return res
message_id = fields.Many2one('mail.message', string='Message')
message_body = fields.Html(related='message_id.body', string='Message to move', readonly=True)
message_from = fields.Char(related='message_id.email_from', string='From', readonly=True)
message_subject = fields.Char(related='message_id.subject', string='Subject', readonly=True)
message_moved_by_message_id = fields.Many2one('mail.message', related='message_id.moved_by_message_id', string='Moved with', readonly=True)
message_moved_by_user_id = fields.Many2one('res.users', related='message_id.moved_by_user_id', string='Moved by', readonly=True)
message_is_moved = fields.Boolean(string='Is Moved', related='message_id.is_moved', readonly=True)
parent_id = fields.Many2one('mail.message', string='Search by name', )
model = fields.Selection(_model_selection, string='Model')
res_id = fields.Integer(string='Record')
can_move = fields.Boolean('Can move', compute='get_can_move')
move_back = fields.Boolean('MOVE TO ORIGIN', help='Move message and submessages to original place')
partner_id = fields.Many2one('res.partner', string='Author')
filter_by_partner = fields.Boolean('Filter Records by partner')
message_email_from = fields.Char()
message_name_from = fields.Char()
# FIXME message_to_read should be True even if current message or any his childs are unread
message_to_read = fields.Boolean(related='message_id.to_read')
uid = fields.Integer()
move_followers = fields.Boolean(
'Move Followers',
help="Add followers of current record to a new record.\n"
"You must use this option, if new record has restricted access.\n"
"You can change default value for this option at Settings/System Parameters")
@api.depends('message_id')
@api.one
def get_can_move(self):
# message was not moved before OR message is a top message of previous move
self.can_move = not self.message_id.moved_by_message_id or self.message_id.moved_by_message_id.id == self.message_id.id
@api.onchange('move_back')
def on_change_move_back(self):
if not self.move_back:
return
self.parent_id = self.message_id.moved_from_parent_id
model = self.message_id.moved_from_model
if self.message_id.is_moved:
self.model = model
self.res_id = self.message_id.moved_from_res_id
@api.onchange('parent_id', 'res_id', 'model')
def update_move_back(self):
model = self.message_id.moved_from_model
self.move_back = self.parent_id == self.message_id.moved_from_parent_id \
and self.res_id == self.message_id.moved_from_res_id \
and (self.model == model or (not self.model and not model))
@api.onchange('parent_id')
def on_change_parent_id(self):
if self.parent_id and self.parent_id.model:
self.model = self.parent_id.model
self.res_id = self.parent_id.res_id
else:
self.model = None
self.res_id = None
@api.onchange('model', 'filter_by_partner', 'partner_id')
def on_change_partner(self):
domain = {'res_id': [('id', '!=', self.message_id.res_id)]}
if self.model and self.filter_by_partner and self.partner_id:
fields = self.env[self.model].fields_get(False)
contact_field = False
for n, f in fields.iteritems():
if f['type'] == 'many2one' and f['relation'] == 'res.partner':
contact_field = n
break
if contact_field:
domain['res_id'].append((contact_field, '=', self.partner_id.id))
if self.model:
res_id = self.env[self.model].search(domain['res_id'], order='id desc', limit=1)
self.res_id = res_id and res_id[0].id
else:
self.res_id = None
return {'domain': domain}
@api.one
def check_access(self):
cr = self._cr
uid = self.env.user.id
operation = 'write'
context = self._context
if not ( self.model and self.res_id ):
return True
model_obj = self.pool[self.model]
mids = model_obj.exists(cr, uid, [self.res_id])
if hasattr(model_obj, 'check_mail_message_access'):
model_obj.check_mail_message_access(cr, uid, mids, operation, context=context)
else:
self.pool['mail.thread'].check_mail_message_access(cr, uid, mids, operation, model_obj=model_obj, context=context)
@api.multi
def open_moved_by_message_id(self):
message_id = None
for r in self:
message_id = r.message_moved_by_message_id.id
return {
'type': 'ir.actions.act_window',
'res_model': 'mail_move_message.wizard',
'view_mode': 'form',
'view_type': 'form',
'views': [[False, 'form']],
'target': 'new',
'context': {'default_message_id': message_id},
}
@api.multi
def move(self):
for r in self:
r.check_access()
if not r.parent_id or not (r.parent_id.model == r.model and
r.parent_id.res_id == r.res_id):
#link with the first message of record
parent = self.env['mail.message'].search([('model','=',r.model), ('res_id','=',r.res_id)], order='id', limit=1)
r.parent_id = parent.id or None
r.message_id.move(r.parent_id.id, r.res_id, r.model, r.move_back, r.move_followers)
if not ( r.model and r.res_id ):
obj = self.pool.get('ir.model.data').get_object_reference(self._cr, SUPERUSER_ID, 'mail', 'mail_archivesfeeds')[1]
return {
'type' : 'ir.actions.client',
'name' : 'Archive',
'tag' : 'reload',
'params' : {'menu_id': obj},
}
return {
'name': _('Record'),
'view_type': 'form',
'view_mode': 'form',
'res_model': r.model,
'res_id': r.res_id,
'views': [(False, 'form')],
'type': 'ir.actions.act_window',
}
@api.one
def delete(self):
self.message_id.unlink()
return {}
@api.model
def create_partner(self, message_id, relation, partner_id, message_name_from, message_email_from):
model = self.env[relation]
message = self.env['mail.message'].browse(message_id)
if not partner_id and message_name_from:
partner_id = self.env['res.partner'].with_context({'update_message_author': True}).create({
'name': message_name_from,
'email': message_email_from
}).id
context = {'partner_id': partner_id}
if model._rec_name:
context.update({'default_%s' % model._rec_name: message.subject})
fields = model.fields_get()
contact_field = False
for n, f in fields.iteritems():
if f['type'] == 'many2one' and f['relation'] == 'res.partner':
contact_field = n
break
if contact_field:
context.update({'default_%s' % contact_field: partner_id})
return context
@api.one
def read_close(self):
self.message_id.set_message_read(True)
self.message_id.child_ids.set_message_read(True)
return {'type': 'ir.actions.act_window_close'}
class mail_message(models.Model):
_inherit = 'mail.message'
is_moved = fields.Boolean('Is moved')
moved_from_res_id = fields.Integer('Related Document ID (Original)')
moved_from_model = fields.Char('Related Document Model (Original)')
moved_from_parent_id = fields.Many2one('mail.message', 'Parent Message (Original)', ondelete='set null')
moved_by_message_id = fields.Many2one('mail.message', 'Moved by message', ondelete='set null', help='Top message, that initate moving this message')
moved_by_user_id = fields.Many2one('res.users', 'Moved by user', ondelete='set null')
all_child_ids = fields.One2many('mail.message', string='All childs', compute='_get_all_childs', help='all childs, including subchilds')
@api.one
def _get_all_childs(self, include_myself=True):
ids = []
if include_myself:
ids.append(self.id)
while True:
new_ids = self.search([('parent_id', 'in', ids), ('id', 'not in', ids)]).ids
if new_ids:
ids = ids + new_ids
continue
break
moved_childs = self.search([('moved_by_message_id', '=', self.id)]).ids
self.all_child_ids = ids + moved_childs
@api.multi
def move_followers(self, model, ids):
fol_obj = self.env['mail.followers']
for message in self:
followers = fol_obj.sudo().search([('res_model', '=', message.model),
('res_id', '=', message.res_id)])
for f in followers:
self.env[model].browse(ids).message_subscribe([f.partner_id.id], [s.id for s in f.subtype_ids])
@api.one
def move(self, parent_id, res_id, model, move_back, move_followers=False):
if parent_id == res_id:
return
vals = {}
if move_back:
# clear variables if we move everything back
vals['is_moved'] = False
vals['moved_by_user_id'] = None
vals['moved_by_message_id'] = None
vals['moved_from_res_id'] = None
vals['moved_from_model'] = None
vals['moved_from_parent_id'] = None
else:
vals['parent_id'] = parent_id
vals['res_id'] = res_id
vals['model'] = model
vals['is_moved'] = True
vals['moved_by_user_id'] = self.env.user.id
vals['moved_by_message_id'] = self.id
for r in self.all_child_ids:
r_vals = vals.copy()
if not r.is_moved:
# moved_from_* variables contain not last, but original
# reference
r_vals['moved_from_parent_id'] = r.parent_id.id
r_vals['moved_from_res_id'] = r.res_id
r_vals['moved_from_model'] = r.model
elif move_back:
r_vals['parent_id'] = r.moved_from_parent_id.id
r_vals['res_id'] = r.moved_from_res_id
r_vals['model'] = r.moved_from_model
print 'update message', r, r_vals
if move_followers:
r.sudo().move_followers(r_vals.get('model'), r_vals.get('res_id'))
r.sudo().write(r_vals)
r.attachment_ids.sudo().write({
'res_id': r_vals.get('res_id'),
'res_model': r_vals.get('model')
})
def name_get(self, cr, uid, ids, context=None):
if not (context or {}).get('extended_name'):
return super(mail_message, self).name_get(cr, uid, ids, context=context)
if isinstance(ids, (list, tuple)) and not len(ids):
return []
if isinstance(ids, (long, int)):
ids = [ids]
reads = self.read(cr, uid, ids, ['record_name','model', 'res_id'], context=context)
res = []
for record in reads:
name = record['record_name'] or ''
extended_name = ' [%s] ID %s' % (record.get('model', 'UNDEF'), record.get('res_id', 'UNDEF'))
res.append((record['id'], name + extended_name))
return res
def _message_read_dict(self, cr, uid, message, parent_id=False, context=None):
res = super(mail_message, self)._message_read_dict(cr, uid, message, parent_id, context)
res['is_moved'] = message.is_moved
return res
class mail_move_message_configuration(models.TransientModel):
_name = 'mail_move_message.config.settings'
_inherit = 'res.config.settings'
model_ids = fields.Many2many(comodel_name='ir.model', string='Models')
move_followers = fields.Boolean('Move Followers')
@api.model
def get_default_move_message_configs(self, fields):
config_parameters = self.env['ir.config_parameter']
model_obj = self.env['ir.model']
model_names = config_parameters.get_param('mail_relocation_models')
if not model_names:
return {}
model_names = model_names.split(',')
model_ids = model_obj.search([('model', 'in', model_names)])
return {
'model_ids': [m.id for m in model_ids],
'move_followers': config_parameters.get_param('mail_relocation_move_followers')
}
@api.multi
def set_move_message_configs(self):
config_parameters = self.env['ir.config_parameter']
model_names = ''
for record in self:
model_names = ','.join([m.model for m in record.model_ids])
config_parameters.set_param('mail_relocation_models', model_names)
config_parameters.set_param('mail_relocation_move_followers', record.move_followers or '')
class res_partner(models.Model):
_inherit = 'res.partner'
@api.model
def create(self, vals):
res = super(res_partner, self).create(vals)
if 'update_message_author' in self.env.context and 'email' in vals:
mail_message_obj = self.env['mail.message']
# Escape special SQL characters in email_address to avoid invalid matches
email_address = (vals['email'].replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_'))
email_brackets = "<%s>" % email_address
messages = mail_message_obj.search([
'|',
('email_from', '=ilike', email_address),
('email_from', 'ilike', email_brackets),
('author_id', '=', False)
])
if messages:
messages.sudo().write({'author_id': res.id})
return res

122
mail_move_message/mail_move_message_views.xml

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp><data>
<template id="assets_backend" name="custom bar assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/mail_move_message/static/src/css/mail_move_message.css"/>
<script type="text/javascript" src="/mail_move_message/static/src/js/mail_move_message.js"></script>
</xpath>
</template>
<record id="view_wizard" model="ir.ui.view">
<field name="name">mail_move_message.wizard.view</field>
<field name="model">mail_move_message.wizard</field>
<field name="arch" type="xml">
<form string="Move Message">
<field name="can_move" invisible="1"/>
<field name="message_is_moved" invisible="1"/>
<field name="message_name_from" invisible="1"/>
<field name="message_email_from" invisible="1"/>
<field name="message_to_read" invisible="1"/>
<field name="uid" invisible="1"/>
<p attrs="{'invisible':[('can_move', '!=', False)]}">You cannot move this message. It was already moved with a message bellow. Open one and apply changes there.</p>
<group attrs="{'invisible':[('can_move', '!=', False)]}">
<field name="message_moved_by_message_id" context="{'extended_name':1}"/>
<field name="message_moved_by_user_id"/>
<button name="open_moved_by_message_id" string="Open message" type="object" class="oe_highlight"/>
</group>
<group attrs="{'invisible':[('can_move', '=', False)]}" colspan="2">
<label for="model"/>
<div>
<field name="model" widget="polymorphic" polymorphic="res_id" class="oe_inline"/>
</div>
<label for="filter_by_partner"/>
<div>
<field name="filter_by_partner"/>
<field name="partner_id" style="width:50%"/>
<button string="Create Partner" attrs="{'invisible':[('partner_id','!=',False)]}" class="oe_highlight" special="quick_create" field="partner_id" context="{'force_email':True,'default_email':message_email_from,'default_name':message_name_from, 'update_message_author':True}"/>
</div>
<label for="res_id"/>
<div>
<field name="res_id" context="{'extended_name_with_contact':1}" widget="many2one" attrs="{'readonly': [('model','=',False)]}" style="width:50%"/>
<button string="Create new record" name="create_record" type="object" class="oe_highlight" attrs="{'invisible':['|',('model','=',False)]}" special="quick_create" field="res_id" use_for_mail_move_message="True"/>
</div>
<label for="move_back" attrs="{'invisible':[('message_is_moved','=',False)]}"/>
<div attrs="{'invisible':[('message_is_moved','=',False)]}">
<field name="move_back"/>
</div>
<label for="move_followers"/>
<div>
<field name="move_followers"/>
</div>
</group>
<button name="move" string="Move" type="object" class="oe_highlight" attrs="{'invisible':[('can_move', '=', False)]}"/>
<button string="Close" class="" special="cancel" />
<separator string="Message"/>
<group>
<field name="message_subject"/>
<field name="message_from"/>
<field name="message_id" invisible="1"/>
</group>
<div class="openerp mail_move_message">
<div class="oe_mail">
<div class="oe_msg">
<div class="oe_msg_content">
<div class="oe_msg_body">
<!-- use built-in css for messages -->
<field name="message_body"/>
</div>
</div>
</div>
</div>
</div>
<footer>
<button name="read_close" string="Mark as read and Close" type="object" class="oe_highlight"/> or
<button special="cancel" string="Close" class="oe_link"/>
<button name="delete" string="Delete message" type="object" class="oe_highlight oe_right" confirm="Do you really want to delete this message?" attrs="{'invisible':[('uid','!=',1)]}"/>
</footer>
</form>
</field>
</record>
<!-- Relocation config wizard -->
<record id="view_mail_move_message_config_settings" model="ir.ui.view">
<field name="name">relocation settings</field>
<field name="model">mail_move_message.config.settings</field>
<field name="arch" type="xml">
<form string="Configure Mail Relocation" class="oe_form_configuration">
<header>
<button string="Apply" type="object" name="execute" class="oe_highlight"/>
or
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
</header>
<div name="general">
<separator string="Models"/>
<field name="model_ids" widget="many2many_tags"/>
<separator string="Options"/>
<label for="move_followers"/>
<field name="move_followers"/>
</div>
</form>
</field>
</record>
<record id="action_mail_move_message_config" model="ir.actions.act_window">
<field name="name">Mail Relocation</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mail_move_message.config.settings</field>
<field name="view_id" ref="view_mail_move_message_config_settings"/>
<field name="view_mode">form</field>
<field name="target">inline</field>
</record>
<!-- Add menu entry in Settings/Email -->
<menuitem name="Mail Relocation" id="menu_mail_move_message" parent="base.menu_email" sequence="99" action="action_mail_move_message_config"/>
</data>
</openerp>

BIN
mail_move_message/static/description/delete-message.png

After

Width: 1012  |  Height: 546  |  Size: 73 KiB

BIN
mail_move_message/static/description/html-message-viewer.png

After

Width: 1012  |  Height: 546  |  Size: 62 KiB

BIN
mail_move_message/static/description/html-message-viewer1.png

After

Width: 1012  |  Height: 546  |  Size: 51 KiB

BIN
mail_move_message/static/description/html-message.png

After

Width: 890  |  Height: 473  |  Size: 58 KiB

BIN
mail_move_message/static/description/icon.png

After

Width: 120  |  Height: 120  |  Size: 3.0 KiB

BIN
mail_move_message/static/description/inbox-move.png

After

Width: 1054  |  Height: 562  |  Size: 85 KiB

BIN
mail_move_message/static/description/inbox.png

After

Width: 750  |  Height: 400  |  Size: 69 KiB

140
mail_move_message/static/description/index.html

@ -0,0 +1,140 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Mail relocation</h2>
<h3 class="oe_slogan">Relocate customer's mails to a correct place (lead, task etc.)</h3>
</div>
<div class="oe_span12">
<p class="oe_mt32">
The module helps you to organise mails from customers.
</p>
<p>
If customer send mail to user's personal alias (e.g. admin@yourcompany.example.com), a user would be able to relocate such mail to corresponding record.
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2>Usage</h2>
<p class="oe_mt32">
Click on an icon of two cross arrows.
</p>
</div>
<div class="oe_span12">
<div class="oe_demo oe_picture oe_screenshot">
<img src="inbox.png?3"/>
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<p class="oe_mt32">
Find a record you need and click on "Move" button.
</p>
</div>
<div class="oe_span12">
<div class="oe_demo oe_picture oe_screenshot">
<img src="inbox-move.png?4"/>
</div>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<p class="oe_mt32">
Done! You've attached mail and its child mails to the record.
</p>
</div>
<div class="oe_span12">
<div class="oe_demo oe_picture oe_screenshot">
<img src="record1.png?3"/>
</div>
<p class="oe_mt32">
Scroll down... Moved mails are marked by highlighted icon.
</p>
<div class="oe_demo oe_picture oe_screenshot">
<img src="record2.png?4"/>
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<p class="oe_mt32">
You could easily return mails back if you change your mind.
</p>
</div>
<div class="oe_span12">
<div class="oe_demo oe_picture oe_screenshot">
<img src="record-move-back.png?1"/>
</div>
<p class="oe_mt32">
Further information and discussion: <a href="https://yelizariev.github.io/odoo/module/2015/04/10/mail-relocation.html">https://yelizariev.github.io/odoo/module/2015/04/10/mail-relocation.html</a>
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2>Tips and tricks</h2>
<p class="oe_mt32">
Also, the module can be used as a viewer for wide html messages
</p>
</div>
<div class="oe_span12">
<div class="oe_demo oe_picture oe_screenshot">
<img src="html-message.png?1"/>
</div>
<p class="oe_mt32">
Check message and then close window.
</p>
<div class="oe_demo oe_picture oe_screenshot">
<img src="html-message-viewer1.png"/>
</div>
<p class="oe_mt32">
Also, you can mark as read and close
</p>
<div class="oe_demo oe_picture oe_screenshot">
<img src="html-message-viewer.png?2"/>
</div>
<p class="oe_mt32">
In addition, the module can be used to delete a message (it's available for superuser only)
</p>
<div class="oe_demo oe_picture oe_screenshot">
<img src="delete-message.png"/>
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2>Need our service?</h2>
<p class="oe_mt32">Contact us by <a href="mailto:it@it-projects.info">email</a> or fill out <a href="https://www.it-projects.info/page/website.contactus " target="_blank">request form</a></p>
<ul>
<li><a href="mailto:it@it-projects.info">it@it-projects.info <i class="fa fa-envelope-o"></i></a></li>
<li><a href="https://www.it-projects.info/page/website.contactus " target="_blank">
https://www.it-projects.info/page/website.contactus <i class="fa fa-list-alt"></i></a></li>
</ul>
</div>
</div>
</section>

BIN
mail_move_message/static/description/record-move-back.png

After

Width: 904  |  Height: 482  |  Size: 68 KiB

BIN
mail_move_message/static/description/record1.png

After

Width: 1007  |  Height: 537  |  Size: 58 KiB

BIN
mail_move_message/static/description/record2.png

After

Width: 750  |  Height: 400  |  Size: 70 KiB

13
mail_move_message/static/src/css/mail_move_message.css

@ -0,0 +1,13 @@
.openerp .oe_mail .oe_msg .oe_msg_icons .oe_move.oe_moved a {
color: #ffefe;
text-shadow: 0px 1px #961b1b,0px -1px #961b1b, -1px 0px #961b1b, 1px 0px #961b1b, 0px 3px 3px rgba(0,0,0,0.1);
}
.openerp .oe_mail .oe_msg .oe_msg_icons .oe_move:hover a {
color: #ffa1a1;
text-shadow: 0px 1px #ff0000,0px -1px #ff0000, -1px 0px #ff0000, 1px 0px #ff0000, 0px 3px 3px rgba(0,0,0,0.1);
}
.openerp.mail_move_message{
width: 864px;
}

91
mail_move_message/static/src/js/mail_move_message.js

@ -0,0 +1,91 @@
openerp.mail_move_message = function (session) {
var _t = session.web._t,
_lt = session.web._lt;
var mail = session.mail;
mail.ThreadMessage.include({
bind_events: function(){
this._super.apply(this, arguments);
this.$('.oe_move').on('click', this.on_move_message)
},
on_move_message: function(event){
var self = this;
var context = {
'default_message_id': this.id
}
var action = {
name: _t('Relocate Message'),
type: 'ir.actions.act_window',
res_model: 'mail_move_message.wizard',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
target: 'new',
context: context,
};
self.do_action(action, {
'on_close': function(){
self.check_for_rerender();
}
});
}
})
mail.MessageCommon.include({
init: function (parent, datasets, options) {
this._super(parent, datasets, options);
this.is_moved = datasets.is_moved || false;
}
})
session.web.form.WidgetButton.include({
on_click: function() {
if(this.node.attrs.special == 'quick_create'){
var self = this;
var related_field = this.field_manager.fields[this.node.attrs['field']];
var context_built = $.Deferred();
if(this.node.attrs.use_for_mail_move_message) {
var model = new session.web.Model(this.view.dataset.model);
var partner_id = self.field_manager.fields['partner_id'].get_value();
var message_name_from = self.field_manager.fields['message_name_from'].get_value();
var message_email_from = self.field_manager.fields['message_email_from'].get_value();
context_built = model.call('create_partner', [self.view.dataset.context.default_message_id,
related_field.field.relation, partner_id, message_name_from, message_email_from]);
}
else {
context_built.resolve(this.build_context());
}
$.when(context_built).pipe(function (context) {
if(self.node.attrs.use_for_mail_move_message) {
self.field_manager.fields['partner_id'].set_value(context['partner_id']);
}
var pop = new session.web.form.FormOpenPopup(this);
pop.show_element(
related_field.field.relation,
false,
context,
{
title: _t("Create new record"),
}
);
pop.on('closed', self, function () {
self.force_disabled = false;
self.check_disable();
});
pop.on('create_completed', self, function(id) {
related_field.set_value(id);
if(self.field_manager.fields['filter_by_partner']) {
self.field_manager.fields['filter_by_partner'].set_value(true);
}
});
});
}
else {
this._super.apply(this, arguments);
}
},
});
}

8
mail_move_message/static/src/xml/mail_move_message_main.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<template>
<t t-extend="mail.thread.message">
<t t-jquery=".oe_msg_icons" t-operation="append">
<span t-attf-class="oe_move #{widget.is_moved?'oe_moved':''}"><a title="Move to thread" class="oe_e">f</a></span>
</t>
</t>
</template>

1
mail_outgoing/__init__.py

@ -0,0 +1 @@
import mail_outgoing_models

20
mail_outgoing/__openerp__.py

@ -0,0 +1,20 @@
{
'name' : 'Outgoing mails menu',
'version' : '1.0.0',
'author' : 'IT-Projects LLC, Ivan Yelizariev',
'license': 'LGPL-3',
'category' : 'Social Network',
'website' : 'https://yelizariev.github.io',
'description': """
Allows to check outgoing mails, i.e. failed or delayed.
Tested on Odoo 8.0 ab7b5d7732a7c222a0aea45bd173742acd47242d
""",
'depends' : ['mail'],
'data':[
'security/mail_outgoing.xml',
'security/ir.model.access.csv',
'mail_outgoing_views.xml',
],
'installable': False
}

22
mail_outgoing/mail_outgoing_models.py

@ -0,0 +1,22 @@
from openerp.osv import osv
class mail_message(osv.Model):
_inherit = 'mail.message'
def check_access_rule(self, cr, uid, ids, operation, context=None):
group_all_emails = self.pool.get('ir.model.data').xmlid_to_object(cr, uid, 'mail_outgoing.group_all_emails', context=context)
user = self.pool['res.users'].browse(cr, uid, uid, context)
user_groups = set(user.groups_id)
if user_groups.issuperset(group_all_emails):
return
return super(mail_message, self).check_access_rule(cr, uid, ids, operation, context)
class mail_mail(osv.Model):
_name = 'mail.mail'
_inherit = ['mail.mail', 'ir.needaction_mixin']
_needaction = True
def _needaction_domain_get(self, cr, uid, context=None):
return [('state','in', ['outgoing', 'exception'])]

34
mail_outgoing/mail_outgoing_views.xml

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="action_mail_outgoing" model="ir.actions.act_window">
<field name="name">Outgoing</field>
<field name="res_model">mail.mail</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{}</field>
<field name="search_view_id" ref="mail.view_mail_search"/>
<field name="help" type="html">
<p>
Failed or delayed emails will be shown here. You will be able to retry failed emails and send delayed emails manually.
</p>
</field>
</record>
<menuitem name="Outgoing" id="menu_mail_outgoing" parent="mail.mail_feeds" action="action_mail_outgoing" sequence="20"/>
<!-- After installation of the module, open the related menu -->
<record id="action_mail_outgoing_menu" model="ir.actions.client">
<field name="name">Open Outgoing Menu</field>
<field name="tag">reload</field>
<field name="params" eval="{'menu_id': ref('mail_outgoing.menu_mail_outgoing')}"/>
</record>
<record id="base.open_menu" model="ir.actions.todo">
<field name="action_id" ref="action_mail_outgoing_menu"/>
<field name="state">open</field>
</record>
</data>
</openerp>

3
mail_outgoing/security/ir.model.access.csv

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_mail_system,mail.mail.system,model_mail_mail,mail_outgoing.group_all_emails,1,1,1,1

33
mail_outgoing/security/mail_outgoing.xml

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="res.groups" id="group_all_emails">
<field name="name">all_emails</field>
<field name="comment">Gets access to all emails. For example to check outgoing emails.</field>
</record>
<record model="ir.rule" id="rule_mail_mail_user">
<field name="name">mail.mail: user</field>
<field name="model_id" ref="mail.model_mail_mail"/>
<field name="domain_force">[('author_id', '=', user.partner_id.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
<field name="global" eval="False"/>
</record>
<record model="ir.rule" id="rule_mail_mail_system">
<field name="name">mail.mail: system</field>
<field name="model_id" ref="mail.model_mail_mail"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('mail_outgoing.group_all_emails'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="global" eval="False"/>
</record>
</data>
</openerp>

BIN
mail_outgoing/static/description/icon.png

After

Width: 100  |  Height: 100  |  Size: 2.1 KiB

4
mail_partner_lang/README.rst

@ -0,0 +1,4 @@
Use partner language in mail
============================
FIXME: there is issue with frozen dict in new odoo.

1
mail_partner_lang/__init__.py

@ -0,0 +1 @@
import models

12
mail_partner_lang/__openerp__.py

@ -0,0 +1,12 @@
{
'name' : 'Use partner language in mail',
'version' : '1.0.0',
'author' : 'IT-Projects LLC, Ivan Yelizariev',
'license': 'LGPL-3',
'category' : 'Mail',
'website' : 'https://yelizariev.github.io',
'depends' : ['mail'],
'data':[],
'installable': False,
}

88
mail_partner_lang/models.py

@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
from openerp.osv import osv,fields
from openerp import SUPERUSER_ID
class mail_thread(osv.Model):
_inherit = "mail.thread"
def message_track(self, cr, uid, ids, tracked_fields, initial_values, context={}):
def convert_for_display(value, col_info):
if not value and col_info['type'] == 'boolean':
return 'False'
if not value:
return ''
if col_info['type'] == 'many2one':
return value.name_get()[0][1]
if col_info['type'] == 'selection':
return dict(col_info['selection'])[value]
return value
def format_message(message_description, tracked_values):
message = ''
if message_description:
message = '<span>%s</span>' % message_description
for name, change in tracked_values.items():
message += '<div> &nbsp; &nbsp; &bull; <b>%s</b>: ' % change.get('col_info')
if change.get('old_value'):
message += '%s &rarr; ' % change.get('old_value')
message += '%s</div>' % change.get('new_value')
return message
if not tracked_fields:
return True
update_fields = [f for f in tracked_fields]
for browse_record in self.browse(cr, uid, ids, context=context):
p = getattr(browse_record, 'partner_id', None)
if p:
browse_record._context.update({'lang':p.lang})
initial = initial_values[browse_record.id]
changes = set()
tracked_values = {}
# update translation
tracked_fields = self._get_tracked_fields(cr, uid, update_fields, browse_record._context)
# generate tracked_values data structure: {'col_name': {col_info, new_value, old_value}}
for col_name, col_info in tracked_fields.items():
initial_value = initial[col_name]
record_value = getattr(browse_record, col_name)
if record_value == initial_value and getattr(self._all_columns[col_name].column, 'track_visibility', None) == 'always':
tracked_values[col_name] = dict(col_info=col_info['string'],
new_value=convert_for_display(record_value, col_info))
elif record_value != initial_value and (record_value or initial_value): # because browse null != False
if getattr(self._all_columns[col_name].column, 'track_visibility', None) in ['always', 'onchange']:
tracked_values[col_name] = dict(col_info=col_info['string'],
old_value=convert_for_display(initial_value, col_info),
new_value=convert_for_display(record_value, col_info))
if col_name in tracked_fields:
changes.add(col_name)
if not changes:
continue
# find subtypes and post messages or log if no subtype found
subtypes = []
for field, track_info in self._track.items():
if field not in changes:
continue
for subtype, method in track_info.items():
if method(self, cr, uid, browse_record, context):
subtypes.append(subtype)
posted = False
for subtype in subtypes:
subtype_rec = self.pool.get('ir.model.data').xmlid_to_object(cr, uid, subtype, context=context)
if not (subtype_rec and subtype_rec.exists()):
_logger.debug('subtype %s not found' % subtype)
continue
message = format_message(subtype_rec.description if subtype_rec.description else subtype_rec.name, tracked_values)
self.message_post(cr, uid, browse_record.id, body=message, subtype=subtype, context=context)
posted = True
if not posted:
message = format_message('', tracked_values)
self.message_post(cr, uid, browse_record.id, body=message, context=context)
return True

BIN
mail_partner_lang/static/description/icon.png

After

Width: 100  |  Height: 100  |  Size: 2.1 KiB

1
mail_recovery/__init__.py

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

13
mail_recovery/__openerp__.py

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
{
'name': "Mail recovery",
'author': "IT-Projects LLC, Ildar Nasyrov",
'license': 'LGPL-3',
'website': "https://twitter.com/nasyrov_ildar",
'category': 'Uncategorized',
'version': '1.0.0',
'depends': ['mail'],
'data': [
'data.xml',
],
}

10
mail_recovery/data.xml

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="assets_backend" name="message storage" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/mail_recovery/static/src/js/mail_recovery.js"></script>
</xpath>
</template>
</data>
</openerp>

25
mail_recovery/static/src/js/mail_recovery.js

@ -0,0 +1,25 @@
openerp.mail_recovery = function (session) {
var mail = session.mail;
mail.ThreadComposeMessage = mail.ThreadComposeMessage.extend({
bind_events: function () {
var self = this;
this.$('textarea').on('focus', self.on_focus_textarea);
this.$('textarea').on('keyup', self.on_keyup_textarea);
this._super();
},
on_focus_textarea: function(event) {
var $input = $(event.target);
if (!$input.val()) {
$input.val(window.localStorage['message_storage']);
}
},
on_keyup_textarea: function(event) {
window.localStorage['message_storage'] = $(event.target).val();
},
on_message_post: function (event) {
window.localStorage['message_storage'] = '';
return this._super(event);
},
});
};

8
mail_sent/README.rst

@ -0,0 +1,8 @@
Sentbox
=======
Description: https://apps.odoo.com/apps/modules/8.0/mail_sent/
Further information and discussion: https://yelizariev.github.io/odoo/module/2015/02/19/sentbox.html
Tested on Odoo 8.0 231e02693640325c9a05fa31c680063b9e4b017b

1
mail_sent/__init__.py

@ -0,0 +1 @@
import models

15
mail_sent/__openerp__.py

@ -0,0 +1,15 @@
{
'name' : 'Sentbox',
'version' : '1.0.2',
'author' : 'IT-Projects LLC, Ivan Yelizariev',
'license': 'LGPL-3',
'category' : 'Social Network',
'website' : 'https://twitter.com/yelizariev',
'price': 9.00,
'currency': 'EUR',
'depends' : ['mail'],
'data':[
'views.xml',
],
'installable': False
}

9
mail_sent/doc/changelog.rst

@ -0,0 +1,9 @@
.. _changelog:
Changelog
=========
`1.0.2`
-------
- FIX access issue "read access error on delete"

50
mail_sent/i18n/mail_sent.pot

@ -0,0 +1,50 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mail_sent
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-08-11 06:59+0000\n"
"PO-Revision-Date: 2015-08-11 06:59+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: mail_sent
#: model:ir.actions.client,help:mail_sent.action_mail_sent_feeds
msgid "<p>\n"
" No message found and no message sent yet.\n"
" </p><p>\n"
" Click on the top-right icon to compose a message. This\n"
" message will be sent by email if it's an internal contact.\n"
" </p>\n"
" "
msgstr ""
#. module: mail_sent
#: model:ir.model,name:mail_sent.model_mail_message
msgid "Message"
msgstr ""
#. module: mail_sent
#: model:ir.model,name:mail_sent.model_mail_notification
msgid "Notifications"
msgstr ""
#. module: mail_sent
#: model:ir.actions.client,name:mail_sent.action_mail_sent_feeds
#: model:ir.ui.menu,name:mail_sent.mail_sentfeeds
#: field:mail.message,sent:0
msgid "Sent"
msgstr ""
#. module: mail_sent
#: help:mail.message,sent:0
msgid "Was message sent to someone"
msgstr ""

58
mail_sent/i18n/sl.po

@ -0,0 +1,58 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mail_sent
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-08-11 06:59+0000\n"
"PO-Revision-Date: 2015-08-11 09:01+0200\n"
"Last-Translator: Matjaz Mozetic <m.mozetic@matmoz.si>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: \n"
"Language: sl\n"
"X-Generator: Poedit 1.8.2\n"
#. module: mail_sent
#: model:ir.actions.client,help:mail_sent.action_mail_sent_feeds
msgid ""
"<p>\n"
" No message found and no message sent yet.\n"
" </p><p>\n"
" Click on the top-right icon to compose a message. This\n"
" message will be sent by email if it's an internal contact.\n"
" </p>\n"
" "
msgstr ""
"<p>\n"
" Nobeno sporočilo ni bilo najdeno in ni bilo še poslano.\n"
" </p><p>\n"
" Pritisnite na ikono zgoraj desno za sestavljanje sporočila. To\n"
" sporočilo bo, če gre za interni stik, poslano preko e-pošte.\n"
" </p>\n"
" "
#. module: mail_sent
#: model:ir.model,name:mail_sent.model_mail_message
msgid "Message"
msgstr "Sporočilo"
#. module: mail_sent
#: model:ir.model,name:mail_sent.model_mail_notification
msgid "Notifications"
msgstr "Obvestila"
#. module: mail_sent
#: model:ir.actions.client,name:mail_sent.action_mail_sent_feeds
#: model:ir.ui.menu,name:mail_sent.mail_sentfeeds field:mail.message,sent:0
msgid "Sent"
msgstr "Poslano"
#. module: mail_sent
#: help:mail.message,sent:0
msgid "Was message sent to someone"
msgstr "Je bilo sporočilo komu poslano"

28
mail_sent/models.py

@ -0,0 +1,28 @@
from openerp import api, models, fields
class mail_message(models.Model):
_inherit = 'mail.message'
@api.one
@api.depends('author_id', 'notified_partner_ids')
def _get_sent(self):
self_sudo = self.sudo()
self_sudo.sent = len(self_sudo.notified_partner_ids) > 1 or len(self_sudo.notified_partner_ids) == 1 and self_sudo.author_id and self_sudo.notified_partner_ids[0].id != self_sudo.author_id.id
sent = fields.Boolean('Sent', compute=_get_sent, help='Was message sent to someone', store=True)
class mail_notification(models.Model):
_inherit = 'mail.notification'
def _notify(self, cr, uid, message_id, **kwargs):
super(mail_notification, self)._notify(cr, uid, message_id, **kwargs)
self.pool['mail.message'].browse(cr, uid, message_id)._get_sent()
class mail_compose_message(models.TransientModel):
_inherit = 'mail.compose.message'
sent = fields.Boolean('Sent', help='dummy field to fix inherit error')

BIN
mail_sent/static/description/icon.png

After

Width: 100  |  Height: 100  |  Size: 2.1 KiB

47
mail_sent/static/description/index.html

@ -0,0 +1,47 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Sentbox</h2>
<h3 class="oe_slogan">Quick way to check sent messages</h3>
</div>
<div class="oe_span6">
<div class="oe_row_img oe_centered">
<img class="oe_picture oe_screenshot" src="menu.png"/>
</div>
</div>
<div class="oe_span6">
<p class="oe_mt32">
The module adds usual menu.
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<p class="oe_mt32">
This menu shows messages in threads (like in other menus), but shows only sent messages.
</p>
</div>
<div class="oe_span12">
<div class="oe_demo oe_picture oe_screenshot">
<img src="messages.png?1"/>
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2>Need our service?</h2>
<p class="oe_mt32">Contact us by <a href="mailto:it@it-projects.info">email</a> or fill out <a href="https://www.it-projects.info/page/website.contactus " target="_blank">request form</a></p>
<ul>
<li><a href="mailto:it@it-projects.info">it@it-projects.info <i class="fa fa-envelope-o"></i></a></li>
<li><a href="https://www.it-projects.info/page/website.contactus " target="_blank">
https://www.it-projects.info/page/website.contactus <i class="fa fa-list-alt"></i></a></li>
</ul>
</div>
</div>
</section>

BIN
mail_sent/static/description/menu.png

After

Width: 217  |  Height: 167  |  Size: 5.8 KiB

BIN
mail_sent/static/description/messages.png

After

Width: 625  |  Height: 217  |  Size: 37 KiB

38
mail_sent/views.xml

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="action_mail_sent_feeds" model="ir.actions.client">
<field name="name">Sent</field>
<field name="tag">mail.wall</field>
<field name="context">{
'default_model': 'res.users',
'default_res_id': uid,
'thread_model': 'res.partner',
'needaction_menu_ref': ['mail.mail_tomefeeds', 'mail.mail_starfeeds', 'mail.mail_inboxfeeds']
}</field>
<field name="params" eval="&quot;{
'domain': [
('author_id.user_ids', 'in', [uid]),
('sent', '=', True),
],
'view_mailbox': True,
'show_compose_message': False
}&quot;"/>
<field name="help" type="html">
<p>
No message found and no message sent yet.
</p><p>
Click on the top-right icon to compose a message. This
message will be sent by email if it's an internal contact.
</p>
</field>
</record>
<record id="mail_sentfeeds" model="ir.ui.menu">
<field name="name">Sent</field>
<field name="sequence" eval="10"/>
<field name="action" ref="action_mail_sent_feeds"/>
<field name="parent_id" ref="mail.mail_feeds"/>
</record>
</data>
</openerp>

2
mail_todo_custom/README.rst

@ -0,0 +1,2 @@
Mark unstarred email as read, remove filter in 'To-do' folder
=============================================================

0
mail_todo_custom/__init__.py

13
mail_todo_custom/__openerp__.py

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
{
'name': "Mark unstarred email as read, remove filter in 'To-do' folder",
'version': '1.0',
'author': 'IT-Projects LLC',
'license': 'LGPL-3',
'website': "https://yelizariev.github.io",
'category': 'Tools',
'depends': ['mail'],
'data': ['mail_todo_custom.xml'],
'demo': [],
'installable': False,
}

25
mail_todo_custom/mail_todo_custom.xml

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="assets_backend_inherited_mail_todo_custom" name="Mail todo custom" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/mail_todo_custom/static/src/js/mail_todo_custom.js"></script>
</xpath>
</template>
<record id="mail.action_mail_star_feeds" model="ir.actions.client">
<field name="context">
{
'default_model': 'res.users',
'default_res_id': uid,
'thread_model': 'res.partner',
'search_default_message_unread': False,
'view_todo': True
}
</field>
</record>
</data>
</openerp>

44
mail_todo_custom/static/src/js/mail_todo_custom.js

@ -0,0 +1,44 @@
openerp.mail_todo_custom = function(session) {
var mail = session.mail;
mail.ThreadMessage.include({
on_star: function (event) {
event.stopPropagation();
var self=this;
var button = self.$('.oe_star:first');
this.ds_message.call('set_message_starred', [[self.id], !self.is_favorite, true])
.then(function (star) {
self.is_favorite=star;
if (self.is_favorite) {
button.addClass('oe_starred');
} else {
button.removeClass('oe_starred');
}
if (self.options.view_inbox && self.is_favorite) {
self.on_message_read_unread(true);
}
else {
self.check_for_rerender();
}
if (self.options.view_todo && !self.is_favorite) {
self.on_message_read_unread(true);
}
});
return false;
},
});
mail.Widget.include({
init: function (parent, action) {
this._super(parent, action);
this.action.params.view_todo = this.action.context.view_todo || false;
},
});
}

4
mail_wall_custom/README.rst

@ -0,0 +1,4 @@
Custom mail wall
================
Tested on Odoo 8.0 ab7b5d7732a7c222a0aea45bd173742acd47242d

1
mail_wall_custom/__init__.py

@ -0,0 +1 @@
import models

25
mail_wall_custom/__openerp__.py

@ -0,0 +1,25 @@
{
'name' : 'Custom mail wall',
'version' : '1.0.0',
'author' : 'IT-Projects LLC, Ivan Yelizariev',
'license': 'LGPL-3',
'category' : 'Custom',
'website' : 'https://yelizariev.github.io',
'depends' : ['gamification',
'gamification_extra',
'hr',
'sale',
'sales_team',
'crm',
'calendar',
'project',
'mail_wall_widgets',
'sale_mediation_custom',
'access_custom',
],
'data':[
'views.xml',
'data.xml',
],
'installable': False,
}

402
mail_wall_custom/data.xml

@ -0,0 +1,402 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="goal_average_payment_time" model="gamification.goal.definition">
<field name="name">Average payment time</field>
<field name="description"></field>
<field name="monetary" eval="False"/>
<field name="suffix">days</field>
<field name="computation_mode">avg</field>
<field name="display_mode">progress</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="field_id" ref="sale_mediation_custom.field_sale_order_invoice_deal_time"/>
<field name="field_date_id" ref="sale.field_sale_order_date_order"/>
<field name="condition">lower</field>
<field name="domain">[('user_id','=', user.id),('state','in', ['done'])]</field>
</record>
<record id="goal_average_payment_time_all" model="gamification.goal.definition">
<field name="name">Average payment time [ALL]</field>
<field name="description"></field>
<field name="monetary" eval="False"/>
<field name="suffix">days</field>
<field name="computation_mode">avg</field>
<field name="display_mode">progress</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="field_id" ref="sale_mediation_custom.field_sale_order_invoice_deal_time"/>
<field name="field_date_id" ref="sale.field_sale_order_date_order"/>
<field name="condition">lower</field>
<field name="domain">[('state','in', ['done'])]</field>
</record>
<record id="goal_average_deal_time" model="gamification.goal.definition">
<field name="name">Average deal time</field>
<field name="description"></field>
<field name="monetary" eval="False"/>
<field name="suffix">days</field>
<field name="computation_mode">avg</field>
<field name="display_mode">progress</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="field_id" ref="sale_mediation_custom.field_crm_lead_deal_time"/>
<field name="field_date_id" ref="crm.field_crm_lead_create_date"/>
<field name="condition">lower</field>
<field name="domain">[('user_id','=', user.id), ('sales_funnel_type', 'in', ['won']), ('date_closed', '!=', False)]</field>
</record>
<record id="goal_average_deal_time_all" model="gamification.goal.definition">
<field name="name">Average deal time [ALL]</field>
<field name="description"></field>
<field name="monetary" eval="False"/>
<field name="suffix">days</field>
<field name="computation_mode">avg</field>
<field name="display_mode">progress</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="field_id" ref="sale_mediation_custom.field_crm_lead_deal_time"/>
<field name="field_date_id" ref="crm.field_crm_lead_create_date"/>
<field name="condition">lower</field>
<field name="domain">[('sales_funnel_type', 'in', ['won']), ('date_closed', '!=', False)]</field>
</record>
<record id="goal_oldest_lead" model="gamification.goal.definition">
<field name="name">Oldest lead</field>
<field name="description"></field>
<field name="monetary" eval="False"/>
<field name="suffix">days</field>
<field name="computation_mode">max</field>
<field name="display_mode">progress</field>
<field name="condition">lower</field>
<field name="domain">[('user_id','=', user.id), ('sales_funnel_type', '=', 'lead')]</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="field_id" ref="sale_mediation_custom.field_crm_lead_last_action_time"/>
</record>
<record id="goal_oldest_lead_all" model="gamification.goal.definition">
<field name="name">Oldest lead [ALL]</field>
<field name="description"></field>
<field name="monetary" eval="False"/>
<field name="suffix">days</field>
<field name="computation_mode">max</field>
<field name="display_mode">progress</field>
<field name="condition">lower</field>
<field name="domain">[('sales_funnel_type', '=', 'lead')]</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="field_id" ref="sale_mediation_custom.field_crm_lead_last_action_time"/>
</record>
<record id="goal_oldest_opp" model="gamification.goal.definition">
<field name="name">Oldest Opportunity</field>
<field name="description"></field>
<field name="monetary" eval="False"/>
<field name="suffix">days</field>
<field name="computation_mode">max</field>
<field name="display_mode">progress</field>
<field name="condition">lower</field>
<field name="domain">[('user_id','=', user.id), '|', ('sales_funnel_type', '=', 'quotation'),('sales_funnel_type', '=', 'negotiation')]</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="field_id" ref="sale_mediation_custom.field_crm_lead_last_action_time"/>
</record>
<record id="goal_oldest_opp_all" model="gamification.goal.definition">
<field name="name">Oldest Opportunity [ALL]</field>
<field name="description"></field>
<field name="monetary" eval="False"/>
<field name="suffix">days</field>
<field name="computation_mode">max</field>
<field name="display_mode">progress</field>
<field name="condition">lower</field>
<field name="domain">['|', ('sales_funnel_type', '=', 'quotation'),('sales_funnel_type', '=', 'negotiation')]</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="field_id" ref="sale_mediation_custom.field_crm_lead_last_action_time"/>
</record>
<record id="goal_personal_comission" model="gamification.goal.definition">
<field name="name">Personal comission</field>
<field name="description"></field>
<field name="monetary" eval="True"/>
<field name="computation_mode">python</field>
<field name="display_mode">progress</field>
<field name="condition">higher</field>
<field name="domain">[('state','!=','cancel'),('user_id','=',user.id),('type','=','out_invoice')]</field><!-- duplicated at compute_code-->
<field name="model_id" ref="account.model_account_invoice_report"/>
<field name="field_id" ref="account.field_account_invoice_report_price_total"/>
<field name="field_date_id" eval="ref('account.field_account_invoice_report_date')" />
<field name="compute_code">
personal_comission = object.user_id.employee_ids and object.user_id.employee_ids[0].personal_comission or 0.0
result = object.sum * personal_comission / 100.0
</field>
</record>
<record id="goal_team_bonus" model="gamification.goal.definition">
<field name="name">Team bonus</field>
<field name="description">Monthly team bonus</field>
<field name="monetary" eval="True"/>
<field name="computation_mode">python</field>
<field name="display_mode">progress</field>
<field name="condition">higher</field>
<field name="domain">[('state','!=','cancel'),('section_id','=',user.default_section_id.id),('type','=','out_invoice')]</field><!-- duplicated at compute_code-->
<field name="model_id" ref="account.model_account_invoice_report"/>
<field name="field_id" ref="account.field_account_invoice_report_price_total"/>
<field name="field_date_id" eval="ref('account.field_account_invoice_report_date')" />
<field name="compute_code">
<![CDATA[
minimum = object.user_id.default_section_id.invoiced_forecast
maximum = object.user_id.default_section_id.invoiced_target
current = object.sum
salary = 1000 # TODO
team_bonus = object.user_id.employee_ids and object.user_id.employee_ids[0].team_bonus or 0.0
if current < maximum:
current = maximum
if current < minimum or minimum == maximum:
result = 0.0
else:
result = salary * (team_bonus / (12 * 100)) * (current - minimum) / (maximum - minimum)
result = int(result * 100) / 100.0
]]>
</field>
</record>
<record model="res.groups" id="group_dashboard_accountant">
<field name="name">dashboard_accountant</field>
<field name="category_id" ref="access_custom.module_category_custom"/>
<field name="comment">Shows dashboard to accountant</field>
</record>
<record model="res.groups" id="group_dashboard_sales_management">
<field name="name">dashboard_sales_managment</field>
<field name="category_id" ref="access_custom.module_category_custom"/>
<field name="comment">Shows sales management dashboard</field>
</record>
<record model="res.groups" id="group_dashboard_management">
<field name="name">dashboard_managment</field>
<field name="category_id" ref="access_custom.module_category_custom"/>
<field name="comment">Shows management dashboard. It includes: sales management + accountant dashboard + EXTRA</field>
<field name="implied_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_sales_management')),(4, ref('mail_wall_custom.group_dashboard_accountant'))]"/>
</record>
<record model="res.groups" id="group_dashboard_sales_person">
<field name="name">dashboard_sales_person</field>
<field name="category_id" ref="access_custom.module_category_custom"/>
<field name="comment">Shows dashboard to salesperson</field>
</record>
<record model="res.groups" id="group_dashboard_admin">
<field name="name">dashboard_admin</field>
<field name="category_id" ref="access_custom.module_category_custom"/>
<field name="comment">Shows admin dashboard</field>
</record>
<record id="widget_outstanding_invoices" model="mail.wall.widgets.widget">
<field name="name">Outstanding sale orders</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="domain">[('user_id', '=', user.id),('state', 'in', ['progress', 'manual'])]</field>
<field name="value_field_id" ref="sale.field_sale_order_amount_total"/>
<field name="value_field_monetary" eval="True"/>
<field name="content"><![CDATA[${record.name} - <b>${record.partner_id.name}</b>, ${record.date_order}]]></field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_sales_person'), 0)]"/>
<field name="type">list</field>
<field name="limit">10</field>
<field name="active" eval="True"/>
</record>
<record id="widget_outstanding_invoices_all" model="mail.wall.widgets.widget">
<field name="name">Outstanding sale orders [ALL]</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="domain">[('state', 'in', ['progress', 'manual'])]</field>
<field name="value_field_id" ref="sale.field_sale_order_amount_total"/>
<field name="value_field_monetary" eval="True"/>
<field name="content"><![CDATA[${record.name} - <b>${record.partner_id.name}</b>, ${record.date_order}]]></field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_sales_management'), 0),(4, ref('mail_wall_custom.group_dashboard_accountant'), 0)]"/>
<field name="type">list</field>
<field name="limit">10</field>
<field name="active" eval="True"/>
</record>
<record id="widget_sent_invoices_all" model="mail.wall.widgets.widget">
<field name="name">Sent invoices [ALL]</field>
<field name="model_id" ref="account.model_account_invoice"/>
<field name="domain">[('type', 'in', ['out_invoice']),('state','in',['open'])]</field>
<field name="value_field_id" ref="account.field_account_invoice_amount_total"/>
<field name="value_field_monetary" eval="True"/>
<field name="content"><![CDATA[${record.name} - <b>${record.partner_id.name}</b>, ${record.date_invoice}]]></field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_accountant'), 0)]"/>
<field name="type">list</field>
<field name="limit">10</field>
<field name="active" eval="True"/>
</record>
<record id="widget_received_invoices_all" model="mail.wall.widgets.widget">
<field name="name">Received invoices [ALL]</field>
<field name="model_id" ref="account.model_account_invoice"/>
<field name="domain">[('type', 'in', ['in_invoice']),('state','in',['open'])]</field>
<field name="value_field_id" ref="account.field_account_invoice_amount_total"/>
<field name="value_field_monetary" eval="True"/>
<field name="content"><![CDATA[${record.name} - <b>${record.partner_id.name}</b>, ${record.date_invoice}]]></field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_accountant'), 0)]"/>
<field name="type">list</field>
<field name="limit">10</field>
<field name="active" eval="True"/>
</record>
<record id="widget_tasks" model="mail.wall.widgets.widget">
<field name="name">Task deadlines</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain">[('user_id', '=', user.id),('date_deadline','!=',False)]</field>
<field name="content"><![CDATA[${record.name} - <b>${record.project_id and record.project_id.name or 'NO PROJECT'}</b>, ${record.date_deadline}]]></field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_sales_person'), 0),(4, ref('mail_wall_custom.group_dashboard_sales_management'), 0),(4, ref('mail_wall_custom.group_dashboard_admin'), 0),(4, ref('mail_wall_custom.group_dashboard_accountant'), 0)]"/>
<field name="agenda" eval="True"/>
<field name="field_date_id" ref="project.field_project_task_date_deadline"/>
<field name="type">list</field>
<field name="limit">10</field>
<field name="order">priority DESC</field>
<field name="active" eval="True"/>
</record>
<record id="widget_tasks_all" model="mail.wall.widgets.widget">
<field name="name">Task deadlines [ALL]</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain">[('date_deadline','!=',False)]</field>
<field name="content"><![CDATA[${record.name} - <b>${record.project_id and record.project_id.name or 'NO PROJECT'}</b>, ${record.date_deadline}]]></field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_sales_management'), 0),(4, ref('mail_wall_custom.group_dashboard_accountant'), 0)]"/>
<field name="agenda" eval="True"/>
<field name="field_date_id" ref="project.field_project_task_date_deadline"/>
<field name="type">list</field>
<field name="limit">10</field>
<field name="order">priority DESC</field>
<field name="active" eval="True"/>
</record>
<record id="widget_next_action" model="mail.wall.widgets.widget">
<field name="name">Next action</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="domain">[('user_id', '=', user.id), '|',('date_action','!=',False),('title_action', '!=', False)]</field>
<field name="content"><![CDATA[${record.name}: <b>${record.date_action}</b> - ${record.title_action}]]></field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_sales_person'), 0)]"/>
<field name="agenda" eval="True"/>
<field name="field_date_id" ref="crm.field_crm_lead_date_action"/>
<field name="value_field_id" ref="crm.field_crm_lead_planned_revenue"/>
<field name="value_field_monetary" eval="True"/>
<field name="type">list</field>
<field name="limit">10</field>
<field name="order">priority DESC,probability DESC,planned_revenue DESC</field>
<field name="active" eval="True"/>
</record>
<record id="widget_next_action_all" model="mail.wall.widgets.widget">
<field name="name">Next action [ALL]</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="domain">['|',('date_action','!=',False),('title_action', '!=', False)]</field>
<field name="content"><![CDATA[${record.name}: <b>${record.date_action}</b> - ${record.title_action}]]></field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_sales_management'), 0)]"/>
<field name="agenda" eval="True"/>
<field name="field_date_id" ref="crm.field_crm_lead_date_action"/>
<field name="value_field_id" ref="crm.field_crm_lead_planned_revenue"/>
<field name="value_field_monetary" eval="True"/>
<field name="type">list</field>
<field name="limit">10</field>
<field name="order">priority DESC,probability DESC,planned_revenue DESC</field>
<field name="active" eval="True"/>
</record>
<record id="widget_calls" model="mail.wall.widgets.widget">
<field name="name">Calls</field>
<field name="model_id" ref="crm.model_crm_phonecall"/>
<field name="domain">[('user_id', '=', user.id),('state', 'not in', ['cancel'])]</field>
<field name="content"><![CDATA[${record.name} - <b>${record.partner_id.name}</b>, ${record.date}]]></field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_sales_person'), 0),(4, ref('mail_wall_custom.group_dashboard_sales_management'), 0),(4, ref('mail_wall_custom.group_dashboard_admin'), 0),(4, ref('mail_wall_custom.group_dashboard_accountant'), 0)]"/>
<field name="agenda" eval="True"/>
<field name="field_date_id" ref="crm.field_crm_phonecall_date"/>
<field name="type">list</field>
<field name="limit">10</field>
<field name="order">date DESC</field>
<field name="active" eval="True"/>
</record>
<record id="widget_calls_all" model="mail.wall.widgets.widget">
<field name="name">Calls [ALL]</field>
<field name="model_id" ref="crm.model_crm_phonecall"/>
<field name="domain">[('state', 'not in', ['cancel'])]</field>
<field name="content"><![CDATA[${record.name} - <b>${record.partner_id.name}</b>, ${record.date}]]></field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_management'), 0)]"/>
<field name="agenda" eval="True"/>
<field name="field_date_id" ref="crm.field_crm_phonecall_date"/>
<field name="type">list</field>
<field name="limit">10</field>
<field name="order">date DESC</field>
<field name="active" eval="True"/>
</record>
<record id="widget_meetings" model="mail.wall.widgets.widget">
<field name="name">Meetings</field>
<field name="model_id" ref="calendar.model_calendar_event"/>
<field name="domain">[('partner_ids', 'in', [user.partner_id.id])]</field>
<field name="content"><![CDATA[${record.name} - ${record.start_datetime}]]></field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_sales_person'), 0),(4, ref('mail_wall_custom.group_dashboard_sales_management'), 0),(4, ref('mail_wall_custom.group_dashboard_admin'), 0),(4, ref('mail_wall_custom.group_dashboard_accountant'), 0)]"/>
<field name="agenda" eval="True"/>
<field name="field_date_id" ref="calendar.field_calendar_event_start_datetime"/>
<field name="type">list</field>
<field name="limit">10</field>
<field name="order">start_datetime DESC</field>
<field name="active" eval="True"/>
</record>
<record id="widget_meetings_all" model="mail.wall.widgets.widget">
<field name="name">Meetings [ALL]</field>
<field name="model_id" ref="calendar.model_calendar_event"/>
<field name="domain">[]</field>
<field name="content"><![CDATA[${record.name} - ${record.start_datetime}]]></field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_management'), 0)]"/>
<field name="agenda" eval="True"/>
<field name="field_date_id" ref="crm.field_crm_phonecall_date"/>
<field name="agenda" eval="True"/>
<field name="field_date_id" ref="calendar.field_calendar_event_start_datetime"/>
<field name="type">list</field>
<field name="limit">10</field>
<field name="order">start_datetime DESC</field>
<field name="active" eval="True"/>
</record>
<record id="widget_sales_funnel" model="mail.wall.widgets.widget">
<field name="name">Conversion Rate</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="domain">[('user_id','=', user.id), ('sales_funnel_type', 'in', ['won', 'lost'])]</field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_sales_person'), 0)]"/>
<field name="type">funnel</field>
<field name="value_field_id" ref="crm.field_crm_lead_planned_revenue"/>
<field name="value_field_monetary" eval="True"/>
<field name="stage_field_id" ref="sale_mediation_custom.field_crm_lead_stage_closed_id"/>
<field name="won_domain">[('sales_funnel_type', 'in', ['won'])]</field>
<field name="active" eval="True"/>
</record>
<record id="widget_sales_funnel_all" model="mail.wall.widgets.widget">
<field name="name">Conversion Rate [ALL]</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="domain">[('sales_funnel_type', 'in', ['won', 'lost'])]</field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_sales_management'), 0)]"/>
<field name="type">funnel</field>
<field name="value_field_id" ref="crm.field_crm_lead_planned_revenue"/>
<field name="value_field_monetary" eval="True"/>
<field name="stage_field_id" ref="sale_mediation_custom.field_crm_lead_stage_closed_id"/>
<field name="won_domain">[('sales_funnel_type', 'in', ['won'])]</field>
<field name="active" eval="True"/>
</record>
<record id="widget_sales_contributed" model="mail.wall.widgets.widget">
<field name="name">Sales contributed</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="domain">[('state', 'in', ['done'])]</field>
<field name="won_domain">[('user_id','=', user.id), ('state', 'in', ['done'])]</field>
<field name="group_ids" eval="[(4, ref('mail_wall_custom.group_dashboard_sales_person'), 0)]"/>
<field name="type">slice</field>
<field name="value_field_id" ref="sale.field_sale_order_amount_total"/>
<field name="value_field_monetary" eval="True"/>
<field name="active" eval="True"/>
</record>
<!--
<record id="goal_average_deal_time" model="gamification.goal.definition">
<field name="name">Average deal time</field>
<field name="description"></field>
<field name="monetary" eval="False"/>
<field name="suffix"></field>
<field name="computation_mode"></field>
<field name="display_mode">progress</field>
<field name="condition">higher</field>
<field name="domain"></field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="field_id" ref="sale_mediation_custom.field_crm_lead_last_action_time"/>
</record>
-->
</data>
</openerp>

13
mail_wall_custom/models.py

@ -0,0 +1,13 @@
from openerp import api,models,fields
from openerp.osv import fields as old_fields
class hr_employee(models.Model):
_inherit = 'hr.employee'
default_section_id = fields.Many2one('crm.case.section', 'Default Sales Team', related='user_id.default_section_id')
personal_comission = fields.Float('Personal comission', help='Personal comission for sales. Value 1.0 is equal 1%')
team_bonus = fields.Float('Team bonus', help='Maximum team bonus (per year). Value 1.0 is equal 1%')
company_bonus = fields.Float('Company bonus', help='Maximum team bonus (per year). Value 1.0 is equal 1%')

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

Loading…
Cancel
Save