diff --git a/addons/account_payment/account_payment_view.xml b/addons/account_payment/account_payment_view.xml index f66f02a6d12..e49d6dfe93b 100644 --- a/addons/account_payment/account_payment_view.xml +++ b/addons/account_payment/account_payment_view.xml @@ -41,7 +41,7 @@ - + diff --git a/addons/document/report/document_report.py b/addons/document/report/document_report.py index c9df78e4272..a4133f46387 100644 --- a/addons/document/report/document_report.py +++ b/addons/document/report/document_report.py @@ -31,7 +31,7 @@ class report_document_user(osv.osv): 'name': fields.char('Year', size=64,readonly=True), 'month':fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'), ('05','May'), ('06','June'), ('07','July'), ('08','August'), ('09','September'), ('10','October'), ('11','November'), ('12','December')],'Month',readonly=True), - 'user_id':fields.integer('Owner', readonly=True), + 'user_id': fields.many2one('res.users', 'Owner', readonly=True), 'user': fields.related('user_id', 'name', type='char', size=64, readonly=True), 'directory': fields.char('Directory',size=64,readonly=True), 'datas_fname': fields.char('File Name',size=64,readonly=True), diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 6e19b1eb8aa..904481096d3 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -411,7 +411,17 @@ class email_template(osv.osv): # create a mail_mail based on values, without attachments values = self.generate_email(cr, uid, template_id, res_id, context=context) assert values.get('email_from'), 'email_from is missing or empty after template rendering, send_mail() cannot proceed' - del values['partner_to'] # TODO Properly use them. + + # process partner_to field that is a comma separated list of partner_ids -> recipient_ids + # NOTE: only usable if force_send is True, because otherwise the value is + # not stored on the mail_mail, and therefore lost -> fixed in v8 + values['recipient_ids'] = [] + partner_to = values.pop('partner_to', '') + if partner_to: + # placeholders could generate '', 3, 2 due to some empty field values + tpl_partner_ids = [pid for pid in partner_to.split(',') if pid] + values['recipient_ids'] += [(4, pid) for pid in self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)] + attachment_ids = values.pop('attachment_ids', []) attachments = values.pop('attachments', []) msg_id = mail_mail.create(cr, uid, values, context=context) @@ -420,11 +430,11 @@ class email_template(osv.osv): # manage attachments for attachment in attachments: attachment_data = { - 'name': attachment[0], - 'datas_fname': attachment[0], - 'datas': attachment[1], - 'res_model': 'mail.message', - 'res_id': mail.mail_message_id.id, + 'name': attachment[0], + 'datas_fname': attachment[0], + 'datas': attachment[1], + 'res_model': 'mail.message', + 'res_id': mail.mail_message_id.id, } context.pop('default_type', None) attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context=context)) diff --git a/addons/email_template/tests/test_ir_actions.py b/addons/email_template/tests/test_ir_actions.py index d323ee36c1f..9b408a45316 100644 --- a/addons/email_template/tests/test_ir_actions.py +++ b/addons/email_template/tests/test_ir_actions.py @@ -33,7 +33,7 @@ class TestServerActionsEmail(TestServerActionsBase): 'name': 'TestTemplate', 'email_from': 'myself@example.com', 'email_to': 'brigitte@example.com', - 'partner_to': '[%s]' % self.test_partner_id, + 'partner_to': '%s' % self.test_partner_id, 'model_id': self.res_partner_model_id, 'subject': 'About ${object.name}', 'body_html': '

Dear ${object.name}, your parent is ${object.parent_id and object.parent_id.name or "False"}

', diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py index ccc8a49b875..a43bbe79713 100644 --- a/addons/email_template/tests/test_mail.py +++ b/addons/email_template/tests/test_mail.py @@ -21,6 +21,7 @@ import base64 from openerp.addons.mail.tests.common import TestMail +from openerp.tools import mute_logger class test_message_compose(TestMail): @@ -200,3 +201,44 @@ class test_message_compose(TestMail): # Generate messsage with default email and partner on template mail_value = mail_compose.generate_email_for_composer(cr, uid, email_template_id, uid) self.assertEqual(set(mail_value['partner_ids']), set(send_to), 'mail.message partner_ids list created by template is incorrect') + + @mute_logger('openerp.osv.orm', 'openerp.osv.orm') + def test_10_email_templating(self): + """ Tests designed for the mail.compose.message wizard updated by email_template. """ + cr, uid, context = self.cr, self.uid, {} + + # create the email.template on mail.group model + group_model_id = self.registry('ir.model').search(cr, uid, [('model', '=', 'mail.group')])[0] + email_template = self.registry('email.template') + email_template_id = email_template.create(cr, uid, { + 'model_id': group_model_id, + 'name': 'Pigs Template', + 'email_from': 'Raoul Grosbedon ', + 'subject': '${object.name}', + 'body_html': '${object.description}', + 'user_signature': True, + 'email_to': 'b@b.b c@c.c', + 'email_cc': 'd@d.d', + 'partner_to': '${user.partner_id.id},%s,%s,-1' % (self.user_raoul.partner_id.id, self.user_bert.partner_id.id) + }) + + # not force send: email_recipients is not taken into account + msg_id = email_template.send_mail(cr, uid, email_template_id, self.group_pigs_id, context=context) + mail = self.mail_mail.browse(cr, uid, msg_id, context=context) + self.assertEqual(mail.subject, 'Pigs', 'email_template: send_mail: wrong subject') + self.assertEqual(mail.email_to, 'b@b.b c@c.c', 'email_template: send_mail: wrong email_to') + self.assertEqual(mail.email_cc, 'd@d.d', 'email_template: send_mail: wrong email_cc') + self.assertEqual( + set([partner.id for partner in mail.recipient_ids]), + set((self.partner_admin_id, self.user_raoul.partner_id.id, self.user_bert.partner_id.id)), + 'email_template: send_mail: wrong management of partner_to') + + # force send: take email_recipients into account + email_template.send_mail(cr, uid, email_template_id, self.group_pigs_id, force_send=True, context=context) + sent_emails = self._build_email_kwargs_list + email_to_lst = [ + ['b@b.b', 'c@c.c'], ['"Followers of Pigs" '], + ['"Followers of Pigs" '], ['"Followers of Pigs" ']] + self.assertEqual(len(sent_emails), 4, 'email_template: send_mail: 3 valid email recipients + email_to -> should send 4 emails') + for email in sent_emails: + self.assertIn(email['email_to'], email_to_lst, 'email_template: send_mail: wrong email_recipients') diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index bae7a0d8f9c..cd417a19d3c 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -19,7 +19,7 @@ # ############################################################################## -from openerp import tools +from openerp import tools, SUPERUSER_ID from openerp.osv import osv, fields @@ -143,9 +143,9 @@ class mail_compose_message(osv.TransientModel): partner_ids.append(partner_id) partner_to = rendered_values.pop('partner_to', '') if partner_to: - for partner_id in partner_to.split(','): - if partner_id: # placeholders could generate '', 3, 2 due to some empty field values - partner_ids.append(int(partner_id)) + # placeholders could generate '', 3, 2 due to some empty field values + tpl_partner_ids = [pid for pid in partner_to.split(',') if pid] + partner_ids += self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context) return partner_ids def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None): diff --git a/addons/hr_applicant_document/__init__.py b/addons/hr_applicant_document/__init__.py new file mode 100644 index 00000000000..76e07d0ac4f --- /dev/null +++ b/addons/hr_applicant_document/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2010-today OpenERP SA () +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see +# +############################################################################## + +import models diff --git a/addons/hr_applicant_document/__openerp__.py b/addons/hr_applicant_document/__openerp__.py new file mode 100644 index 00000000000..bd8224f1755 --- /dev/null +++ b/addons/hr_applicant_document/__openerp__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +{ + 'name': 'Applicant Resumes and Letters', + 'version': '1.0', + 'category': 'Human Resources', + 'sequence': 25, + 'summary': 'Search job applications by Index content.', + 'description': """This module allows you to search job applications by content + of resumes and letters.""", + 'author': 'OpenERP SA', + 'website': 'http://www.openerp.com', + 'depends': [ + 'hr_recruitment', + 'document' + ], + 'data': [ + 'views/hr_applicant.xml' + ], + 'demo': [ + 'demo/hr_applicant.xml' + ], + 'installable': True, + 'auto_install': True, + 'application': True, +} + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/hr_applicant_document/demo/hr_applicant.xml b/addons/hr_applicant_document/demo/hr_applicant.xml new file mode 100644 index 00000000000..46bac2250a0 --- /dev/null +++ b/addons/hr_applicant_document/demo/hr_applicant.xml @@ -0,0 +1,257 @@ + + + + + + JVBERi0xLjQKJcOkw7zDtsOfCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0ZpbHRlci9GbGF0ZURl +Y29kZT4+CnN0cmVhbQp4nJ1UTWvDMAy951f4PKhnKbEdQyk0TXrYrRDYYey2D9hhsF7292fZtdJk +djpG4REqS37vSbKSIL6rL6GE8l/aaYmibUC24vxaPd6Jzxjzv/N71Y2VNj5krRHji7g/ggAlxren +rYIdbhUS1AQNgSYwBHb3PD5Uw1idsuU0Srss2FKeI9jHCh663cYSYglK4UP8KpAA7bz2LIueEgfW +FhQdPYCKpNKFACx9SHQB11U3sLgNKB+aBPHKS6UAWXk3NPswEG0wHMGcgDo1Cyz7HQ620w09ezAw +q5ZPTwFXEu5Q1sKik83S6BtiimGg6YBuNixwmNsFk+CrP3vOwXnbiuxj28AuxoTqjR/ZwUI6+zvj +aUUfBAcDHFPbUEVe/mvaM5dYI/5hxZSRy3HDmm1xvKl6tr3IN8U+66sp34AtjBs5i00cqjVSpkWP +uY3bM6nLG1La3GBwtk5xG27udHGbvSodM6jMwE7RQTRsVx3C+8ukrRrgHx69fAT6VBLslQGzZf3P +I4BULQK1El0arvg6kAFItmMX5495n8QPLYlXWAplbmRzdHJlYW0KZW5kb2JqCgozIDAgb2JqCjQx +MwplbmRvYmoKCjUgMCBvYmoKPDwvTGVuZ3RoIDYgMCBSL0ZpbHRlci9GbGF0ZURlY29kZS9MZW5n +dGgxIDE4MjEyPj4Kc3RyZWFtCnic3XsJXFTXvf8599w7GwPMDDsIXAZxiQgI4hYThlVRtgBuSY2O +zCBEYAgzuD4jxocbGJKagKiN1qbG2DRaaw3GFI1iE7PU2GjaxqapjWljQkyedUkNnLzfOfcOW0zS +z+v//3mfz2PkzrnnnvNbvr/1XBJPXb0TGVEDIshWVm2vvf0+vYYQehMhbClb6pF9ta0/gfFfEBKS +ymsXV3e7UD5CognWPL+4akX5uuXzcuH+ZYTCxAqn3dE4bkMCQpEn4PmECpiIpjVauP8S7odXVHuW +/8b/EQmhqGFwf3+Vq8zesfb8Krhfzu6r7ctrP8LPsOfn4V6usVc7L60/dy/cX4fb67Uut+d+tPZr +hNI+Zs9r65y1jZ2vdiFk0yGkjYA5DB/2Y4Shht0LRJQ0Wp3e4GP09fM3mS0BgUHBIaFh4RHDIqOi +5Rhr7PC4ESNHjb5rDPq/+dOB3uC/+/ET8F3OZzYIa5DQ9+lAp+C5wNd14DfwJvwSjPeiHriuQ9ew +gbyKJ8KoE/bOFWNgtgXt5DtbyN9RPXkZvYPOoIsw+jueTGAvfgfF4A+A2qZ+LqQT7k7BdRXpJHNx +NK5Gz+AXgOIq4OlCawT4FoqB8lviOZh9C22Az1b0DHLBmEm2DuR/Hx1GTeg62iZcQffD+CV0GuSh +yE/hgS+gm0Bpv3CPUA7rTgO17Wg7XocuILeIsAFWXpIuCGOA6mHQAKFFaKd0QdrG8IDvC9IX8ASc +VdOhCdTGghYMt734ZTxOKEDvwP5VqJT8gDxMLuJGMVZcRq6gFgGRheghdFa6oAlELdpY1KIpxyvE +hfyziuknLBMX4v3oCtBcRL6E+xiQbCfXGKHDQrFUIBWAzuUwt5NfW5SrxoTeIrcB9ycEiqeLOSQN +nqwS89A2tAd2jgRkEHKRVODuQqukLcoH7YfPWGkLaQX6HA2cItyDdgrluAmkvQloukgWmgg8IqWr +qBEfBrmRdjVySxcQCkQvajWSSASM4mXTQSEu13HQdt9c+bV5MWPjh9zKJq18EBUd9F0hd3z9ddFc +MUKad1AadpDE6Q6KcbGXvu3hpbHxM4vmyh04JDtLJZu9MAsmS+bCkN3BNMxnZ/FnjOtBKQ7+5S48 +KJdVyJtNm2OnbDY5p4wFm1fQVrFCegaylxZFv4xEDMogDQ48gnXSOkFEiV3nu8ch0/nu891JAeYY +c1yMOaZCRD1uEtHzEW3V+n15rU4zmsWJAFZBYj2goEMBaIwtFLeZUJt+o8Vk0AEhKdl3khlF6VMC +TT3dyT3d5pDJ41DidUYXm1OSJ6SOHxEbF5MsBgWKY7A5Frd80vyjnVtoHj58Gwv0668+eV1K7D37 +ZGPj1r0fXnz/r737GE+MHwKe68BPzKjQFqDR+hBkJm1+nfqTWoNGg3QW0/mu7mRgdBk4vWmebJ6c +ZIszIRM2mWUkY9mchJJwiinJbEM2nGmymYtQES4yFZkt83ECjrVqgsyx5pSge3FKcnCIuO7ulbnP +Hzl6NOH4uqylE8iKhLv+8GbvO+LCi8vWWIcrGGz6+kNxOsjjg0JQLEjUbEHNxk5Le6je4p9FLEH3 +hpqud/cwSEEi09UkDCwCgxX9RyZbzCYh1iqYTRahovnxx5u3PP74liu3bn5y5eZN8sF77164ePHC +u+/tpL+jf6WX6Dt4LKSBKJzAsvYpwGEkeGIAGm8Ll0zYqDuiwc1oh5/mpEEI0CK9pPH19wk0nZ/a +1TO1K3kyA/8ymMF0NdkyeXISDgJdI3FMUAzoG5Makzp+Qoo48lxZPl5L13TQC3jMc7/UBLbft7is +pSeRnGsp6HhB0fcQ8M0CvnrkixJsYbojyNhl2IFOaoQjIsnzwRopD800aP2A8eXunqmgeGJ38uUe +rjq4UhDwMsfglKBYKDy/x9U9NbiaXsLRHR3iwp7ElhaSIWReYfrtBj4GTTRwGWEL0CNiOGI8TY5I +RCMijU+eHuh397zJtQJ0zUyjwOC7mVYjUpk+wpoH5v3+yk9/Rf+IP8Ct//HIzvMnyT8fB/lXgb3m +gr2C0DCUbRuBggk2NOm3aIKPYE2zL34lrDmg07c9kgjDTPpgDcodZjFNj+Qm7DJbGD9mx8umq/C5 +ftXC/AsHxSgWnRjkh2NlZDahlGSLlnuTVpzb8+HxF+aeqK489QD9ir6H5S/evdUhPrGx8XmT8OD9 +mhdfmzT5xTFj8GQcgI3YRv98es9zB3eC/k2gfw7Elh5V24ZpsYAFjVaToRUIOqyTNFgrmMVxWjNK +MkB0cQAAiandYOckSAtak3iG/c6zHgrBGM+3jQgRQrWjhRHaSdo5gkN4SFsvrNQaQjUj8GhNDs7V +zMGLcYVGNx/ND4iB6AQLmWObwEJfnvoaUd0p6cJXKeJbt8eIb32VAhg2Aoax3OdHoxrbXQYtCpeN +of5adCRU22yJaZKPRzYPZzHgi0PFMD+Dxpgli5qge+8CHLsgGSQr4nZdvt7DYsJ0lcUpYGmBYI1M +ik6Sk2KSrLvRbrxb2G3Y7bMneHfI7tDdYbvD/ebzEFXQTp1ojk0FxMHi4yfcjVMVwFOV0I3CQuc9 +P/7JyqptL+CjR+/+ZcPP3vzqH7fw+q0Pnnig/NjcptP3jJCFlIdrnbXvvDQ6r3ftXseCV/YcOxm5 +fsWE8R0jRxYXJ2/l+QZVgx1coKsV/cQ2MtTirxe1KDJCow0yNsukM+JkmEmLzP66fE2BOd+/YFho +fnh2rOn6zIPG0pkHzaUPzD2Kwr8+MWlez1RuJgs309TL18FUTOnJkBWTbNOSxCQpSZOkTdIl6ZMM +ST5pwWkhaaFpYWnhaRFpw9Ii06IaSIPYIDVoGrQNugZ9g6HBpyW4JaQltCWsJbwlomVYS2RLVCye +j7n2YTjWDNlVSWZ9AzWrCc9YK2dscO1LzSm6e9/kGbmTn302piwtz0muTs8+Rz/oXSas/cy96qPe +NcLaL2rZt7hw4dS0HIh9DIEpfCSO5LUjwuYr7EHPi3s0Eo4UkQ7inefd7uuQ31MYO8jpV3rgRxxJ +z9HJ9KyCJ6sZBeDX4Du2YE2bKLShjbo28RcGLOnHaUkUSjHyetHVxYh185wZwNKU+nuaLO0tFR7r +rROO9yyTLuynOft7P9yv0GY5MQJsFYDibaF6P4IIxLS503jSANGDZvjqNT45geCDySwfJ4IVWFGC +AL4DSmLE0YceXtvU0TFun/tnzwlHemcIR9oee/FnvRvEhfsWll1Scj8tFqPFZaDLMJRiGxZq1JNm +f31zUKd/e8SpsJORoUaNJnwasljuVTJIcjJLH5d5IVBTlloIrRrMPJkVAua9qQy8vA2NjZs2NTZu +EMLHtjvPXPn4dUd7wtGjwph337t44cLF93o3l8zDE7EZB+Mpc0pa/nlTwQBiM0J8cAgG7QZ80sj0 +nwFAcAx6lCqZrGIQAPZSpGGGU8LqVMeS2kc3Hz067rmHn9+H9zEQGATCiq/27LM7Lqnx8fWH5GWw +pxn4hRi1OowOiW3+ujbDRv92i14b5YNSLaae8z1d3gp487dM9YmpUZhzY/XApAFu1Q+crGh5mK4T +pr1za2HnPd333ffUGyRrf4+F/u2TeIXXBdBNA/YdieptNl+j4OcTEh2l0wtaQ0hUdFRGZFSowScq +WgxCTfiEGNgUdCK02Sw2x3Wa20dFGnyiI7SoMELjl6vVBFqzR5mudwEIl1lS96Z1E71x1XTjqiWE +pyKeR/0+A3i0/DrPioMS1PzDynBQIBN7pKrI+EQhAbMqmhxM3ircXbJ61Q9enLFpS/fvSo48tPjl +0pXrb+iyd/3wvdfv3ytOPpyQcF/JzBmxfuE7V+89FhvbmZpaNq9hnOAXvXXNjw/EcN+KA19+HnAV +kAFts6WjOIxESRTiJFELv5o4SRL1ccSgg18cZyAGFAeNNTFkIKJtwwfJRr2k12k1vDHVS4kGH9Of +unkDNhVqsVc53Wdak/SZVvk3YKT7DAqHlrDCEW5A/tBFtyCRYB9BJEZRr9Xo1uNNAisWmEApx7Ek +hsQKFRfwfvqLG/jMuZreG1XnpNhekbxwewxupKuQWnct0DeYIFaSoGNBgeHNfoHNuna/TryDhEBV +F6aZLT7T+yMlsbs/UPqcUo2QgZFD7u/oSGh3vHHlk9ed26n/xsbGpqbGxo3knJD5z+4tJXMwtAUQ +JRPnUJ933/vTeYgc5ks31fpqgP4ikLA8JG3UQvuqi9VEERSLfaDB6OpR2hfetEIjLKXGpbAcBLE6 +hb6Ai17HE3pe2y/W53XMuH1BzUHbgG4snIuGoftso9GwOEkjwdmZhETEaTRShsn8rG9bYKuI2gRk +MgjYEBViNZHhkaYe4HXihFIfGMPrbyomkrj/wVdI8jxrHGufJqCJ92JFc6XYQQet0fphXC+80FN/ +DIemOnK2NjzwWu3iV+0Xsc88x6QL+/fvP40T7l3ZVrj6sYzMN8clX/n1whOe9L8xu7zC+iGwi49i +l74c1u7fGbEjDHLXNJ7FcphdkpXEOcAuStrkxuClR+lnR4wMgkRKWjc2rt+8eX3jxt4P43eUv/7x +lTcggXV0CIksgZ3/03vC8uK59Az9nH5KT88p2cKOVRhOb0jUSU9D/hpnC/WTdP7kCDLjk7ojBp2P +Ho4VGpPFb3BX2319aherpsxNWJEY1Aea4Ri3hz74YP3ZS2f3s7ZWepqebOnd/R+Ltu59Q1jYgu+F +M8VWekm4hhORhEJtPuQnaI+GiDgSWMEp6E1IDT2spAWR2AB8s+f09jKceJauxWsUezcBfuPA3mHQ +DYeH/RIdCm4jvr80HjK16VtJe3hAshGN0ySFs5qmHgJ4EryaFOdtFmMg4fPEovj2hIniuPw9D0C/ +fwqn4cgH9uTP2D/7dFfX6bnP5aaOHo1bcS2uwe2jR5+9x0bfpm/R39K3bff01z9+Jhhc/3bcuf51 +37H+mb+n/mkCe5/hBVBAnXS2WAr52KTUv76Y7vRrx6fIyUiI52k8sgd4z8D6F9dXb1W/GeRPy+Dc +BWH9949fL2/HX2xQfGnDlt4zGkNLyRz6G/oJuM6ZOfiG6k6KPWYDBisgro2o0RavE4igJUQA/bFe +EHCGgTXQOuihfSA9Qm6U4NAoJho0KMlX6aND1D66izVmSgSe8XbTbAyJ0d+A0XzbcEln0IfgUBKi +C9VDG01G6EbrJ+DJZIJukt7PXwsfA5mPoZ/WY3bGwrEA82zIHAIWsf8xerOd3nhJutCrE768PUYa +2fMZCbj9R7Xvnwjya1CqLUyKAwVIHCT+DAn6JCIR2GxF0VoTO45MVg7U3kTBfudZoVcCVjFN5Pne +T94RdL2p0oXZt9dKY5i9ylkvy2uKL8qwRWNf4gs1wxdqho+2TcJQNLDRgKJ0osbfONzP1NNznrep +rE9gI8uAswVDAhQjKWDAWH6EFD6go/HvYz559dUzvRukyJ5PyVs9Kc/QndhxHEHHyM4MeRAn/JyM +DbZw8lO9pS3K2BbaGtU+PCgqQhODIqz+UTHRw0E5SLimq96O4fzVJNu7iSgRJwqJJFFMlBI1idpE +XaI+0ZDok4bScJqQRtLENClNk6ZN06Xp0wxpPoWoEBcKhYZCnwVoAV4gLDAs8NmFduFdwi6yS9wl +7dLs0u7S7dLvMuzyOYAO4APCAXJAPCAd0BzQHtAd0B8wHPA5jo7j48Jxclw8Lh3XHNce1x3XHzcc +98n5NmFUVmSBuEBaoFmgXaBboGeMv43QcHAQzHsGpZkIUHMAC4eBLwfwh/kTJhUVTp40s3FLU9OW +x5qaHvv8xo3PP79+Xbg6saho4qSCPGEnJIMz9HX6Nk7CE6AvTNpNl9O19FG6HG/Ej+A1eCOPD+jh +xbn8XDncFqD23+IvdBLmzfd4g9J8K438wNY79gpx9f5AcPbueIN13dP3907s6zfjIAeEoURbuLHJ +94QJNYWdCG4mpmZ9J0uAFiPS5IRDt+XNfyZ6nXVZd0iA3pOdGDfjR8W0h17EcVi8b3tB7tb5P3/p +2PMLt6VPhqPyJBwIn0l3xb+SPuUvb5+9dPe9zLfr6TXxB+BfZhSJ7rHJKMpk9o9oI0E8DZujzKEm +oz8KtSQbJ4Qma1KjvKdmdggDsbq4o8EZFHxcsmpiB9hBG6IN8NoBZoQWbbju9q1bPb23NjW91mba +sL19w4b27RsiBNyA1+MNuIE20BX0Pdo+vdNBbuFcHE7/Rl+kHfRvOAziYC/g/zB/Z2ZGMkgaG4ba +DPo2y0bcZvhFtNknLDpAJ0jILypYSh42To+iLEkxygu08zxbT+47Fk3uf49GBrxQY66kZd2pOBrj +hz8/2Pr0vs8//eG6tU/SGfilj75ct27rs/Qm/SfNEc70vr9qyw83CeX03trVDzv2/uZXm54ODD67 ++42zgGcEfUXcLrkhXsejWtu4UWOGh4cQP6M+Yjw5EKI/YDSPHH5gTEjUgXFjUkf6akaBkccEonDf +BGNgzBjj2IRU6Dd7ukx0aheken60ZyifZ1NdF7rgyD95sjLND/2+6tE2oCGwIUjDYqK/DgT0H/L5 +0Yg9UrxmwsSQfheyBIl+DUuWrF27ZElD8+Nx64p//M47Py5aP/zgEzfp+3gGtkyompyUtmoK3UHn +4+V46rnbQszaHTvWPrpjB/3YlZZ17eWXr+Xck7azE2LmKNhrZWDQhrBQ/DvcDFb9Lb35F+gT+PkX +jwMrmo6gPQK0CKLpPO+I+JGXHXfxOH7QFdAaiIscnu+Go7tt1gANagv+mcm3yfiYqc2qaRvWam2P +C9AQHG3VRxlHhEXHmXoug0v2vSi8yWKEvV3ytrmABuFnDYuZN8Kp4y0psoXpbh0hlK7dunXt+o0b +LqU9ln/idOL+mj989o8/Y/EafZ9+mvuk0PrSM8+89Iufv3Cwd9NLcSNxDA5zLMGG6/+F9XQzddEN +1B2N1Pc6LEeHgszgmdrosDZDdJvJ8DMRN6HHxLbgVlN7nDUKjfC1ajXDcAATursb5O5rbD5S3uYk +YbWh4bU9KFCIlQUl0Ae+/iQfMGl/k8Skvf4+7bkGhStwxpP0/bVPPgnKbJQOg7D0A/p3xxJ66x/X +6E1cj5/AK/GW6N4qr0I8B7F3sBNB7lFoGvTcAW1Bhib9Xt82TXSTvHdYW2yrpj3oudHBAYgEhkWN +MEURa3SgPno09NxdalSB/N19nRmPLB5AA493I8bg1L4jR3/jTXRbd9JPbyx+d3H5bxbtPXRo2/bt +TTufWD+vs2LFr3Pfw9ImEj3y1ad+++mI4WdSx7duebR978pq96pRo16S5Yu/WvWM0rO0gPxjISew +zGUVtZHhbVpzk+mxwDZflp1927X7oyA3h0RhgxWZoqPYYaFLrY9eX6FdEEc8WTPJUFAgGiQ8k/ms +cL23a8yc+I+xiX54a9npgh+8ZH/2V8eevW97DsvoT5j86dVPuukXsvxW8riDe3YfiosDT18HshVw +P45FiSjTFhdqRG0jNW1RY9ssULlHPpcUahx+V1TQ8Ch/fVRQhJVE+cdEJ4GI3VxG3uip8Jp42A96 +Ax7nPTP3Ja1Y63CYCRhwvhMqNmx9qnHj1qfo62ufuPb2uWtPrG3dRenly/TrXfkNK1Y2rFm1okE4 +3bZ5c3tb86ZtpTGH1xw6d+7QmsMxMa/tev3yh2d2n8GLlj/yyPKVDWsVvMHXS1ee+3PxuAX+U2+g +aB3/s98bzz25o/+PgLRYfF4DQYF0fVOwT1tNIwf8pRAP+cshFs+hCnQaP8T+cocOod1oFfRzjewd +IuSO0zDH5qvRBTjds781bUOvoHV4K6w5hTqhc22CDq0RKvQpVA91IgL2rIH7TXD6Xsfpj0AL0T50 +Gcu4FIrNecEqbCPgGcRGashe8oU4SnxQPC9Nlf5TOqjRaYo0mzX7NFe1I7S12re1VDdVj/Sj9FX6 +E4ZAw9OGwz5+Pg/5vG4MNT6qahKPUsHeylt+E2pnmktzsBlORSLMBWO/Pn0X9OmOYeUCdSzAOpc6 +hgyJ3OpYhPE6dSxBX75VHWuQH9qljlk1/Lk69oE46FTHvvqt6KI69oPupAYoY1EPd52GPeoYI9ln +oToWkM6nQR0TmN+ojkUY71PHcMrzeU0da9Awn7+oYx2y+vSoYx80xRiljn0DRhjnqmM/VBH9SKar +dkVd5eIKjzyqbLScnJSUIi9aIWdUetyeOqe9Ol7OrSlLkNOrquRitsotFzvdzrqlTkeCIcv5kH12 +vVxWYa9Z7HTL9jqnXFkj19Yvqqoskx2uantljXdNib3GLee7alwZLteSoXND72c769yVrho5OSEl +RXnGHg1YWe6qAUE8IF6Fx1M7JTHRAfNL6xPcrvq6Mme5q26xM6HG6cnhy5hYTLU+leRRbqdTXuSs +ci0bnSD/C0okGAz9m0E4u6xQ7oPOMPY7fwyG/znI8hDOlSCi7KmzO5zV9rolsqt8KBWDochZV13p +5gjC6gpnnRN4La6z13icjni5vA6Uh22gMMAUL3tcsr1mhVwLmMMG1yIPKFxZsxi4lIHQbKWnwqki +bi8rc1XXwnK2wFMB1AEkZ40bALZySKyjgZhDtrvdrrJKO/ADBMvqq501HruHyVNeWQUYj2IU+Qa5 +xFXuWQaYW0dzSeqctXUuR32Zk5NxVIJilYvqPU4uw6AN8WClsqp6B5NkWaWnwlXvAWGqK1VGbH2d +AiWQrXfDeqZOvFzt5Fpz+7or4gfwiGc8E111stsJdoDVlSCqqv4Q1kw4IFvLgPao0HFGyypc1d/c +wMxQXl9XAwydfKPDJbtd8bK7ftFDzjIPm1EwrgKXZAqVuWoclUwP9xSDoRQe2Re5ljq5BooXcQH6 +nKDG5QEzuJVZZpXafg9QnsnuCjsotcipogZigJPbB+npqgG/qJOrXXXOO6ote1bUOsvtwChBEWrw +02r7Cka/2uWoLK9kjmav8oDrwQCI2h0OrrkCHYsvex3IVV9lr+OMHE535eIaLsbiqhW1FW62iXmo +vQyIuNkOrzzuoZwUj3MogNmrBhAYQkTd55WlnyKIWFO1Qq4c5OqgUp2T/SdIfC0buBmYzDbeEHGC +3zkVBZa56hxu2doXi1bG2/tAtrLQtXLYwDp5aswsckI0Mar1YAemxFJXZZ9gzuUeiBrZXlsLIWZf +VOVkDxT9gfIQw1TYPXKF3Q0UnTWDcQF2/R7ukOtrHKrA1sF5xapo+F2WdbuqWGRz0zFD2eUqlkEg +XrwLa+1lS+yLQTGIxRpXX/741x1rECtIWiCis6qcCTU9W84pLCiVSwpzSuekF2fLuSVyUXHh7Nys +7CzZml4C99Z4eU5u6fTCWaUyrChOLyidJxfmyOkF8+SZuQVZ8XL23KLi7JISubBYzs0vysvNhrnc +gsy8WVm5BdPkDNhXUFgq5+Xm55YC0dJCvlUllZtdwojlZxdnTofb9IzcvNzSefFyTm5pAaOZA0TT +5aL04tLczFl56cVy0aziosKSbKCRBWQLcgtyioFLdn42KAGEMguL5hXnTpteGg+bSmEyXi4tTs/K +zk8vnhnPJCwElYtlviQBpAQacvZstrlkenpenpyRW1pSWpydns/WMnSmFRTmM4xmFWSll+YWFsgZ +2aBKekZetiIbqJKZl56bHy9npeenT8su6WfClqnq9MPBNkzLLsguTs+Ll0uKsjNz2QBwzC3Ozizl +KwF7QCKPi5tZWFCSfd8smIB1XhZgkOnZnAUokA7/MrlkXP0CUJfRKS0sLu0TZU5uSXa8nF6cW8JE +yCkuBHGZPWEH03EW4MmMV6DKy2zE5r7pHbCK7VYVzMpOzwOCJUyMb6wF78peXuas9TDfVoNbSY88 +lSr5M557rZIEwIWn1UDgKnN8CP4MkcUrj5Lh+oOLleR4Nf2y9AHeDdVISb+OpU7Igm6WSiA+XCyZ +LKt080iHMljtUuue214FzGBX3yrIl/Yq2ObuE3NwQHkLYm1dJWxZVlfpgWQi2+thtq5ypVqK69RS +NVQDxmWo/HVOdy1UqsqlzqoVCbC2jtUzLkllDbRb1arqHL4yzxRvDvXIizlxBygOTVmCbPjOfi1x +WeWSysRKyFHLE2orahPVRIkyoROvRStQHapEi+FM4kEynI7L0Gj4TkZJ8EmB0SJYIaMMWOOBbt0D +q53IDueTeJjNRTWwPgFG6agKPjIq7qPl5ndO+HbCnqVwdcBKA8qC0UNAYTacX2TYXQHjGtjj5Dvs +nL4MVGrgWgtrFgHdSlgnw34X8LXzZ0PplHAqjEI+rKqB3wz4daEl37vu+57P5vK7gauLy5QMWqTA +Z+A+76470yznswoiHhU9hpAH9JsC5+VE0ExZvxTWJ8A6F3zXgc5OvreOo5MANJywJ2cANS9aXqt9 +00rsGbOAk1vSCVi60DJYy2z2/8YSzKaGO3JWkLPDaKDM3/Q6Axr7b3wY9/8NT74z2v06V6ooyvy5 +ndu4mqO6BOZcYNnvk4VpVsTpVXNq/T6o0K7gz5yqXos5lxruYQ5Op5w/dfZxUyyseFM8l8vFJazh ++2tVP1c4uICqR7VwJfcKRZcyFWkvTQ+XYrCP22FVGfeQWpW6lwJbrciueJKTR43iwdYBXmLllmN7 +HfzbzeUqgz12VT/FB8vAK6s5FQ9/4sWnHEZVqh+P6pOxnwOLcya/B2JB8XPGsR8TNlMLVxdwqedy +9kvj4Bp4uK8tgqce/tTL49s5xKuxVAaS1XMqCibLuA9U8Jj3qMhU87mBGnnp1w3ySkXaeo5h/ADr +sHE1t6fX1v3x64bd8d+iR3yfnok878icshIPCu1KFdXB1v9urb3IKdLW9nm0Z4jX9Wu0jONR/S9x +8EZDOc+ZNaqGzgEcHfzKeMTzb4bEQ7CijNNT1gz04yo1S3otVMZ5O7jElaqkU3h0lqq77EDRxTND +vw0G5qJ+BL6ZCVi98KjR4B601hsr/YgNzAED98lcZ7tqqUV9edvrawoaSia3f4c9XbzGyKrtq/l3 +f/74V2zhAc1red2yqxolDELqu/YyTFb0yV/No6+Sx7I3ozHZPWrWU2YUSRmmjgE2H+h13vrFuCh4 +1QMVO9/n1cjBJWX2qhmAxmJYx7SpUOfqBuRQO/cexXe9PIbi4/5enQbmOMcgD7NzG91Jgu+WZDC/ +objcScZ41e5VfF/ld2T1OjUDObl81YPoemfcfZ7pjZuhVcSp5jvnIAss41o5+H7rHeqitU/voTvY +em/VtQ7wNiV28obUmUU87l0DZK1X48FriaXwtPIOiDnRco5zjRrRtfBRqpidZ1Zn346B9ldk/u6I +qeCZXubfblVGJ/eob/cXRbs75XD2tJ6vGozwnVCVByA30Ib/05h18+zprdn9UeeNKNZBVPX1IHXq +jsEUa7lHL4HrYtViSl2s4dgO7T/+f2Ssb9dqkRojHrUulvchNR1lcz6FqADuGJ9CuCtFc6CfLObP +cmFOhn6uGJ7MhrssmM3idknnT9hzK4/GOTBmFAvRLE5LoVEMV0Z7Hsww2jK/Z3czYX0B0GJ7s9Fc +ziMbqJXwlcWcdj7M5sF3trqO7ciEmVlwz8bTEOtGFX4FsKuUxw7bx2RRJC2F+X6ug6XK5Ry9kuXD +XTHQn64+TQfauZwekz+eI8XGBX1y5qiSpnOMGGVGMxMkyuN3bHYWfBfBuhKOZzrXWZG2gOuQA88V +XbK5BIolFIky4bsIeLMV00CuUi4F41SqroznGjJ9svh+xnUmn1UkK1StzMb9VBJULBU5GP6z+ziX +cP3z4CNz/UthppTbJh3oe+l6fWcap5Df50ezuH7pHIdCziGDP2MoMjzz+lYWD7BKJseL2Y1JnsU5 +pXNESu6oiZfaYOvcyTu8HKZx/bI5Unl8dQngmA3rc/tmFH/M5bpmqtgqNBW/V3wibwC6mVxHZtn7 +gGu26lPpHLvBWigRwuTv10KxQLp6zRyAWb/1C1TrZvbZupB72TdRmcNjMZuvSue2LulDIYfHb74q ++awBHua14yzVPwv7JBuMrzeOvOv+ldyh0PLyHmzBLO5PeaqEJX1ofD9dJXdlQ10r4+cdT1/eHly5 +B3aP/V3pwP4zfkCuHdgJKFl4Gl9bPWRd/6ySn5Wa1X/mGdjD3alyeU/JSk/f3/16uw8ldytno4Hd +r4P36Uov6O7rSpT64errTJbxp/01XTkNVvMVA897bs5X0axe3TGUltJf2nm3wLi574Dmd1WooSfE +Wl7vFS7L+NijdiZMv3p1LZtfOeRUXDfkVPV9NvDq8n3413F716pnqkqOMOsnE1S6dch7PuvHhCGg +vN2qHmL1fu9j1KagoX0ow2DxAMkdqsWVN2WMp+HfeL+WyPFeAr+JXEYH7/wSeBdeC3ODO0rlv4VA +Xz+C7kd3+DkqNOCgQ0/Ol9KH4SDUighcG/j/T0thHMCvFmSBq5mPTfzqj7bD1Y+PfQ99Ok1Kj8O+ +aDXcGVEcXH1QMlwNnJ6er9IhP7hq+VjD10h8LPJ5wmcEPoNt8yihlPSuJj2UfEXJ7WTyz2Pky9Xk +1s1m6RYlt06IN2/Mk242k5sN4o3rI6Qb88gNm3h9BPnHtUTpH7fJtUTyX5R8QcnnyeRqIPmslXSD +iN2UdHd8fc72tfjpNPLJFYf0SSu54iAfU/L3v0VIf6fkbxHkI0ouLyEfUvLXY+TSX8KkS7fJX8LI +B63kz5S8T8mfLgZJf6LkYhB5r5X88Q9B0h8p+cMWH+kPQeT3q8m7U8gFuLkwhZyn5J3fGaR3KPmd +gZyj5G1Kzm42S2eHkd8Gk7coebOVvNEUJ71ByeuUnFlNXqPkVUp+Q8np7b5SFyWnKDlJySuUnAB6 +JwLJcSPp/PUxqZOSX788X/r1MfLrBvHlY3HSy/PJyzbxWBx5iZKjraSjJV16kZIj8HXkNvkV0DpM +yS8d5JCD/MKPHLSQA5S8QG295OeUPE/JzyxkPyXP7fOTnksm+/zIs3vN0rOjyF4z+ekzY6WfribP +jCU/oWQPJT+mZPeuMGm3g+x62iTtCiNPm8iPDGQnJTuAyQ5KtvuS9m0JUjsl2xJIG/BvayWtTx2T +Wil5CnzrqWPkqQbxycfjpCfnkydt4lZKfkjJE3D/xDHyeBxpATBa0sljoO1jgWSLD2mGiWYHaQLQ +muLIZjPZRMlGSjZQsr7RLK2npNFM/pOSdZQ8as6QHi0haylpWE7WPLJaWkPJI6vJ6ijyH5Ss8iMr +KVlGyVJK6j1Gqd6f1HdgZHtP9BiJ54TothC3Tayj5GFKailx1ZRIrlZSUz1Kqikh1aNIFSVLkslD +lFQmk4rbZPExUk6JkxIHJWWLoqQyShYhk7QoitgpWUjJAkoevN9HetCPzHeQH7xGHoCbBwLJ/T4E +PHpuIJlDyWxKZkWESbOSSSklJZQUU3LfalJESWEgKaAkH4+V8inJO0ZmjiIzckOlGRNJbqZFyg0l +07NDpemUTIO7aQ6SA3c5x0h2KMmCiayJJDPDLGVaSGaHYLPpxYx0fynDTDI6BAR36TY/Kd2fpHfg +E3BnSzNKNj9i68ANcJdm1EtpRpLWgW02h3gvJfeACPfcJlMpuXsUmULJZAB4soNMGhcuTZpJJlIy +YWygNIGS1JlkfFK4NH4mSYGvFEqSYWEyJePg8bhwkhROEmGUGEoS9MFSwjEyNj5AGhtIxnYIjG28 +ySzFB5B4Jm6rOOauOGkMJXfByrviyGhhijSaklGUjKRkhD+JC86Q4rLJcH8SS4nV31+yUhIjj5Vi +VhN5LImeSaKAcxQlkZQMA2yHURIBVokII+GUhFESSkkIUAjJIcFBY6XgDBIUaJKCxpJAEwmAdQGB +xAL7LZSYQXNzBjEBB5OZmBTs/P2Mkr8/8Vew8/M1SH5G4qdg5wvY+RqIL2B3WDTqiZH51kTRhxID +aGKgRB9MdCaipUQDpDWUSIGEgHLkNhFgQphCMAiAxxJkIrgDOxq34DH/d37Q/7YA/+ZPJPpvgO3r +dQplbmRzdHJlYW0KZW5kb2JqCgo2IDAgb2JqCjEwNDEzCmVuZG9iagoKNyAwIG9iago8PC9UeXBl +L0ZvbnREZXNjcmlwdG9yL0ZvbnROYW1lL0JBQUFBQStEZWphVnVTYW5zTW9ubwovRmxhZ3MgNQov +Rm9udEJCb3hbLTU1NyAtMzc0IDcxNiAxMDQxXS9JdGFsaWNBbmdsZSAwCi9Bc2NlbnQgOTI4Ci9E +ZXNjZW50IC0yMzUKL0NhcEhlaWdodCAxMDQxCi9TdGVtViA4MAovRm9udEZpbGUyIDUgMCBSCj4+ +CmVuZG9iagoKOCAwIG9iago8PC9MZW5ndGggNDE2L0ZpbHRlci9GbGF0ZURlY29kZT4+CnN0cmVh +bQp4nF2TzW7iMBSF93kKLzuLKvEPCZVQJAaKxKIz1dA+QEgME2lwIhMWvH197klbaRagz/G9l8/m +JN/st/vQT/lrHNqDn9SpD1301+EWW6+O/tyHTBvV9e00r+S7vTRjlqfew/06+cs+nIbVKsv/pL3r +FO/qYd0NR/8jy3/Hzsc+nNXD++aQ1ofbOP7zFx8mVWR1rTp/SnNemvFXc/G5dD3uu7TdT/fH1PJd +8HYfvTKy1lRph85fx6b1sQlnn62Kolar3a7OfOj+23OWLcdT+7eJqVSn0qJYFHViI1wZsBUud2BH +LsEL8hO4JG/AFXkBXgq7Z/ATn2vwmrwF/xQ28rsbYbsGb9krc55ZL3N2dMMcXZBRo+nv0Ktnfwum +v5P62d+B6W/lOf0tzqtnf2H6V1JPf4Mzavo7mU//cgmmf4Wz6NkfZ9T0txWY/hYzDf0t6s3sj7s1 +9C8x39B/AU9Dfwc3Q38nvfQ3uB9Df4v/xdDfykz6W5lJf4t7M/S3SwnJnAbEBXn+jKFqbzGmCEro +JXtIXR/813sxDiO65PMBZrbPkAplbmRzdHJlYW0KZW5kb2JqCgo5IDAgb2JqCjw8L1R5cGUvRm9u +dC9TdWJ0eXBlL1RydWVUeXBlL0Jhc2VGb250L0JBQUFBQStEZWphVnVTYW5zTW9ubwovRmlyc3RD +aGFyIDAKL0xhc3RDaGFyIDQzCi9XaWR0aHNbNjAyIDYwMiA2MDIgNjAyIDYwMiA2MDIgNjAyIDYw +MiA2MDIgNjAyIDYwMiA2MDIgNjAyIDYwMiA2MDIgNjAyCjYwMiA2MDIgNjAyIDYwMiA2MDIgNjAy +IDYwMiA2MDIgNjAyIDYwMiA2MDIgNjAyIDYwMiA2MDIgNjAyIDYwMgo2MDIgNjAyIDYwMiA2MDIg +NjAyIDYwMiA2MDIgNjAyIDYwMiA2MDIgNjAyIDYwMiBdCi9Gb250RGVzY3JpcHRvciA3IDAgUgov +VG9Vbmljb2RlIDggMCBSCj4+CmVuZG9iagoKMTAgMCBvYmoKPDwvRjEgOSAwIFIKPj4KZW5kb2Jq +CgoxMSAwIG9iago8PC9Gb250IDEwIDAgUgovUHJvY1NldFsvUERGL1RleHRdCj4+CmVuZG9iagoK +MSAwIG9iago8PC9UeXBlL1BhZ2UvUGFyZW50IDQgMCBSL1Jlc291cmNlcyAxMSAwIFIvTWVkaWFC +b3hbMCAwIDU5NSA4NDJdL0dyb3VwPDwvUy9UcmFuc3BhcmVuY3kvQ1MvRGV2aWNlUkdCL0kgdHJ1 +ZT4+L0NvbnRlbnRzIDIgMCBSPj4KZW5kb2JqCgo0IDAgb2JqCjw8L1R5cGUvUGFnZXMKL1Jlc291 +cmNlcyAxMSAwIFIKL01lZGlhQm94WyAwIDAgNTk1IDg0MiBdCi9LaWRzWyAxIDAgUiBdCi9Db3Vu +dCAxPj4KZW5kb2JqCgoxMiAwIG9iago8PC9UeXBlL0NhdGFsb2cvUGFnZXMgNCAwIFIKL09wZW5B +Y3Rpb25bMSAwIFIgL1hZWiBudWxsIG51bGwgMF0KL0xhbmcoZW4tSU4pCj4+CmVuZG9iagoKMTMg +MCBvYmoKPDwvQ3JlYXRvcjxGRUZGMDA1NzAwNzIwMDY5MDA3NDAwNjUwMDcyPgovUHJvZHVjZXI8 +RkVGRjAwNEMwMDY5MDA2MjAwNzIwMDY1MDA0RjAwNjYwMDY2MDA2OTAwNjMwMDY1MDAyMDAwMzMw +MDJFMDAzNj4KL0NyZWF0aW9uRGF0ZShEOjIwMTMwOTE3MTA1MjE5KzA1JzMwJyk+PgplbmRvYmoK +CnhyZWYKMCAxNAowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMTIxNDEgMDAwMDAgbiAKMDAwMDAw +MDAxOSAwMDAwMCBuIAowMDAwMDAwNTAzIDAwMDAwIG4gCjAwMDAwMTIyODQgMDAwMDAgbiAKMDAw +MDAwMDUyMyAwMDAwMCBuIAowMDAwMDExMDIxIDAwMDAwIG4gCjAwMDAwMTEwNDMgMDAwMDAgbiAK +MDAwMDAxMTIzOCAwMDAwMCBuIAowMDAwMDExNzIzIDAwMDAwIG4gCjAwMDAwMTIwNTQgMDAwMDAg +biAKMDAwMDAxMjA4NiAwMDAwMCBuIAowMDAwMDEyMzgzIDAwMDAwIG4gCjAwMDAwMTI0ODAgMDAw +MDAgbiAKdHJhaWxlcgo8PC9TaXplIDE0L1Jvb3QgMTIgMCBSCi9JbmZvIDEzIDAgUgovSUQgWyA8 +MUQ3RURGOENDMTExODZGOUQwNERCQTNCNTIxRUIwRUQ+CjwxRDdFREY4Q0MxMTE4NkY5RDA0REJB +M0I1MjFFQjBFRD4gXQovRG9jQ2hlY2tzdW0gL0FGQTcyQ0IwMzY4QzE1RjAyN0YxODgxNDAxQkM3 +QkQ0Cj4+CnN0YXJ0eHJlZgoxMjY1NQolJUVPRgo= + Jones_CV.pdf + Jones_CV.pdf + + hr.applicant + + + UFJPRklMRSANCg0KTmFtZSAgICAgICAgICAgIDogU2hhbmUgV2lsbGlhbXMgIA0KQWRkcmVzcyAgICAgICAgIDogODEgQWNhZGVteSBBdmVudWUsIA0KICAgICAgICAgICAgICAgICAgICAgOkJpcm1pbmdoYW1CNDYgM0FHLCANCiAgICAgICAgICAgICAgICAgICAgIDpVbml0ZWQgS2luZ2RvbSwgDQpRdWFsaWZpY2F0aW9uICAgOiBNQ0EgDQpFbWFpbCAgICAgICAgICAgICA6IFNoYW5lV2lsbGlhbXNAaW5mby5jb20gDQpNb2JpbGUgICAgICAgICAgIDogOTk2MzIxNDU4NyA= + Williams_CV.doc + Williams_CV.doc + + hr.applicant + + + UHJvZmlsZQ0KDQpOYW1lICAgICAgICAgIDpKb3NlDQpBZGRyZXNzICAgICAgIDo5MywgUHJlc3MgQXZlbnVlDQogICAgICAgICAgICAgICAgICAgOkxlIEJvdXJnZXQgZHUgTGFjLCA3MzM3NywNCiAgICAgICAgICAgICAgICAgICA6IEZyYW5jZSANClF1YWxpZmljYXRpb24gOk1DQQ0KRW1haWwgICAgICAgICAgIDpKb3NlQGdtYWlsLmNvbQ0KTW9iaWxlICAgICAgICAgIDo5OTY4NTEzNTg3 + Jose_CV.txt + Jose_CV.txt + + hr.applicant + + + diff --git a/addons/hr_applicant_document/models/__init__.py b/addons/hr_applicant_document/models/__init__.py new file mode 100644 index 00000000000..0f3aa35f140 --- /dev/null +++ b/addons/hr_applicant_document/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +import hr_applicant diff --git a/addons/hr_applicant_document/models/hr_applicant.py b/addons/hr_applicant_document/models/hr_applicant.py new file mode 100644 index 00000000000..036ea6c200e --- /dev/null +++ b/addons/hr_applicant_document/models/hr_applicant.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from openerp.osv import fields, osv + + +class hr_applicant(osv.Model): + _inherit = 'hr.applicant' + + def _get_index_content(self, cr, uid, ids, fields, args, context=None): + res = dict.fromkeys(ids, '') + Attachment = self.pool.get('ir.attachment') + attachment_ids = Attachment.search(cr, uid, [('res_model', '=', 'hr.applicant'), ('res_id', 'in', ids)], context=context) + for attachment in Attachment.browse(cr, uid, attachment_ids, context=context): + res[attachment.res_id] += attachment.index_content or '' + return res + + def _content_search(self, cr, user, obj, name, args, context=None): + record_ids = set() + Attachment = self.pool.get('ir.attachment') + args = ['&'] + args + [('res_model', '=', 'hr.applicant')] + att_ids = Attachment.search(cr, user, args, context=context) + record_ids = set(att.res_id for att in Attachment.browse(cr, user, att_ids, context=context)) + return [('id', 'in', list(record_ids))] + + _columns = { + 'index_content': fields.function( + _get_index_content, fnct_search=_content_search, + string='Index Content', type="text"), + } diff --git a/addons/hr_applicant_document/views/hr_applicant.xml b/addons/hr_applicant_document/views/hr_applicant.xml new file mode 100644 index 00000000000..706a1b4c754 --- /dev/null +++ b/addons/hr_applicant_document/views/hr_applicant.xml @@ -0,0 +1,37 @@ + + + + + + Jobs - Recruitment Search + hr.applicant + + + + + + + + + + Resumes and Letters + ir.attachment + tree,form + + [('res_model','=','hr.applicant')] + +

+ Search through resumes and motivation letters. +

+
+
+ + + +
+
+ + diff --git a/addons/hr_holidays/report/holidays_summary_report.py b/addons/hr_holidays/report/holidays_summary_report.py index d39ae53d5b6..adf09a63c85 100644 --- a/addons/hr_holidays/report/holidays_summary_report.py +++ b/addons/hr_holidays/report/holidays_summary_report.py @@ -96,7 +96,7 @@ class report_custom(report_rml): res=cr.fetchone()[0] date_xml=[] date_today=time.strftime('%Y-%m-%d %H:%M:%S') - date_xml +=['' % (res,date_today)] + date_xml +=['' % (to_xml(res),date_today)] cr.execute("SELECT id, name, color_name FROM hr_holidays_status ORDER BY id") legend=cr.fetchall() @@ -128,7 +128,7 @@ class report_custom(report_rml): # date_xml=[] for l in range(0,len(legend)): date_xml += ['' % (l+1,legend[l][0],_(legend[l][1]),legend[l][2])] - date_xml += ['' % (som.strftime('%B'), som.year),''] + date_xml += ['' % (ustr(som.strftime('%B')), som.year),''] cell=1 if day_diff.days>=30: diff --git a/addons/hr_recruitment/doc/changelog.rst b/addons/hr_recruitment/doc/changelog.rst index 404b94ee6d1..7cfce3f677d 100644 --- a/addons/hr_recruitment/doc/changelog.rst +++ b/addons/hr_recruitment/doc/changelog.rst @@ -3,6 +3,13 @@ Changelog ========= +`trunk (saas-3)` +---------------- + +- ``hr.recruitment.stage``: added template_id field. If an email template is linked + to the stage, it is used to render and post a message on the applicant. This + allows for example to have template for accepted or refused applicants. + `trunk (saas-2)` ---------------- diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 8a75f31b4df..c293c689a5b 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -53,6 +53,7 @@ class hr_recruitment_stage(osv.osv): 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of stages."), 'department_id':fields.many2one('hr.department', 'Specific to a Department', help="Stages of the recruitment process may be different per department. If this stage is common to all departments, keep this field empty."), 'requirements': fields.text('Requirements'), + 'template_id': fields.many2one('email.template', 'Use template', help="If set, a message is posted on the applicant using the template when the applicant is set to the stage."), 'fold': fields.boolean('Folded in Kanban View', help='This stage is folded in the kanban view when' 'there are no records in that stage to display.'), @@ -172,6 +173,12 @@ class hr_applicant(osv.Model): res[issue.id][field] = abs(float(duration)) return res + def _get_attachment_number(self, cr, uid, ids, fields, args, context=None): + res = dict.fromkeys(ids, 0) + for app_id in ids: + res[app_id] = self.pool['ir.attachment'].search_count(cr, uid, [('res_model', '=', 'hr.applicant'), ('res_id', '=', app_id)], context=context) + return res + _columns = { 'name': fields.char('Subject / Application Name', size=128, required=True), 'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the case without removing it."), @@ -215,9 +222,9 @@ class hr_applicant(osv.Model): 'day_close': fields.function(_compute_day, string='Days to Close', \ multi='day_close', type="float", store=True), 'color': fields.integer('Color Index'), - 'emp_id': fields.many2one('hr.employee', string='Employee', - help='Employee linked to the applicant.'), + 'emp_id': fields.many2one('hr.employee', string='Employee', help='Employee linked to the applicant.'), 'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True), + 'attachment_number': fields.function(_get_attachment_number, string='Number of Attachments', type="integer"), } _defaults = { @@ -235,11 +242,11 @@ class hr_applicant(osv.Model): } def onchange_job(self, cr, uid, ids, job_id=False, context=None): + department_id = False if job_id: job_record = self.pool.get('hr.job').browse(cr, uid, job_id, context=context) - if job_record and job_record.department_id: - return {'value': {'department_id': job_record.department_id.id}} - return {} + department_id = job_record and job_record.department_id and job_record.department_id.id or False + return {'value': {'department_id': department_id}} def onchange_department_id(self, cr, uid, ids, department_id=False, stage_id=False, context=None): if not stage_id: @@ -290,10 +297,15 @@ class hr_applicant(osv.Model): @return: Dictionary value for created Meeting view """ applicant = self.browse(cr, uid, ids[0], context) + applicant_ids = [] + if applicant.partner_id: + applicant_ids.append(applicant.partner_id.id) + if applicant.department_id and applicant.department_id.manager_id and applicant.department_id.manager_id.user_id and applicant.department_id.manager_id.user_id.partner_id: + applicant_ids.append(applicant.department_id.manager_id.user_id.partner_id.id) category = self.pool.get('ir.model.data').get_object(cr, uid, 'hr_recruitment', 'categ_meet_interview', context) res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'base_calendar', 'action_crm_meeting', context) res['context'] = { - 'default_partner_ids': applicant.partner_id and [applicant.partner_id.id] or False, + 'default_partner_ids': applicant_ids, 'default_user_id': uid, 'default_name': applicant.name, 'default_categ_ids': category and [category.id] or False, @@ -319,6 +331,20 @@ class hr_applicant(osv.Model): value = self.pool.get("survey").action_print_survey(cr, uid, ids, context=context) return value + def action_get_attachment_tree_view(self, cr, uid, ids, context): + domain = ['&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', ids)] + return { + 'name': _('Attachments'), + 'domain': domain, + 'res_model': 'ir.attachment', + 'type': 'ir.actions.act_window', + 'view_id': False, + 'view_mode': 'tree,form', + 'view_type': 'form', + 'limit': 80, + 'context': "{'default_res_model': '%s'}" % (self._name) + } + def message_get_suggested_recipients(self, cr, uid, ids, context=None): recipients = super(hr_applicant, self).message_get_suggested_recipients(cr, uid, ids, context=context) for applicant in self.browse(cr, uid, ids, context=context): @@ -364,6 +390,8 @@ class hr_applicant(osv.Model): def write(self, cr, uid, ids, vals, context=None): if isinstance(ids, (int, long)): ids = [ids] + res = True + # user_id change: update date_start if vals.get('user_id'): vals['date_start'] = fields.datetime.now() @@ -373,8 +401,34 @@ class hr_applicant(osv.Model): for applicant in self.browse(cr, uid, ids, context=None): vals['last_stage_id'] = applicant.stage_id.id res = super(hr_applicant, self).write(cr, uid, [applicant.id], vals, context=context) - return res - return super(hr_applicant, self).write(cr, uid, ids, vals, context=context) + else: + res = super(hr_applicant, self).write(cr, uid, ids, vals, context=context) + + # post processing: if stage changed, post a message in the chatter + if vals.get('stage_id'): + stage = self.pool['hr.recruitment.stage'].browse(cr, uid, vals['stage_id'], context=context) + if stage.template_id: + # TDENOTE: probably factorize me in a message_post_with_template generic method FIXME + compose_ctx = dict(context, + active_ids=ids) + compose_id = self.pool['mail.compose.message'].create( + cr, uid, { + 'model': self._name, + 'composition_mode': 'mass_mail', + 'template_id': stage.template_id.id, + 'same_thread': True, + 'post': True, + 'notify': True, + }, context=compose_ctx) + self.pool['mail.compose.message'].write( + cr, uid, [compose_id], + self.pool['mail.compose.message'].onchange_template_id( + cr, uid, [compose_id], + stage.template_id.id, 'mass_mail', self._name, False, + context=compose_ctx)['value'], + context=compose_ctx) + self.pool['mail.compose.message'].send_mail(cr, uid, [compose_id], context=compose_ctx) + return res def create_employee_from_applicant(self, cr, uid, ids, context=None): """ Create an hr.employee from the hr.applicants """ @@ -394,7 +448,10 @@ class hr_applicant(osv.Model): emp_id = hr_employee.create(cr, uid, {'name': applicant.partner_name or contact_name, 'job_id': applicant.job_id.id, 'address_home_id': address_id, - 'department_id': applicant.department_id.id + 'department_id': applicant.department_id.id or False, + 'address_id': applicant.company_id and applicant.company_id.partner_id and applicant.company_id.partner_id.id or False, + 'work_email': applicant.department_id and applicant.department_id.company_id and applicant.department_id.company_id.email or False, + 'work_phone': applicant.department_id and applicant.department_id.company_id and applicant.department_id.company_id.phone or False, }) self.write(cr, uid, [applicant.id], {'emp_id': emp_id}, context=context) else: diff --git a/addons/hr_recruitment/hr_recruitment_data.xml b/addons/hr_recruitment/hr_recruitment_data.xml index 219f36405b5..492c0f12c73 100644 --- a/addons/hr_recruitment/hr_recruitment_data.xml +++ b/addons/hr_recruitment/hr_recruitment_data.xml @@ -17,6 +17,43 @@ Interview + + + Application refused + Application refused + ${object.email_from} + ${object.partner_id and object.partner_id.id or ''} + ${object.partner_id and object.partner_id.lang or ''} + + + Dear ${object.partner_name or 'applicant'},

+

We thank you for your interest in our company and for your application. + Unfortunately, your profile does not match with our needs or our recruitment + campaign has reached its term.

+

If you want more details, feel free to contact us by phone.

+

Kind regards,

+
+ ${object.user_id and object.user_id.signature or ''}]]>
+
+ + + Application approved + Application approved + ${object.email_from} + ${object.partner_id and object.partner_id.id or ''} + ${object.partner_id and object.partner_id.lang or ''} + + + Dear ${object.partner_name or 'applicant'},

+

Congrats! Your resume's got our interest! + I will call you as soon as possible to make a 10 minutes phone interview and plan a first meeting.

+

If we can’t reach you or if you miss our call, feel free to reach me back on the number 001 312 349 3030 + If I do not answer, please let me a message with some schedules to call you back.

+

Kind regards,

+
+ ${object.user_id.signature}]]>
+
+ @@ -58,6 +95,7 @@ First Interview + 2 @@ -76,6 +114,7 @@ Refused 6 + diff --git a/addons/hr_recruitment/hr_recruitment_demo.xml b/addons/hr_recruitment/hr_recruitment_demo.xml index 8e1435691c8..8ba49a2beb8 100644 --- a/addons/hr_recruitment/hr_recruitment_demo.xml +++ b/addons/hr_recruitment/hr_recruitment_demo.xml @@ -2,10 +2,12 @@ - Salesperson - + Sales Manager + + + 2 Enrique Jones 9963214587 @@ -15,8 +17,9 @@ Trainee - MCA - - + + + 3 @@ -29,10 +32,11 @@ Fresher - - + + + - + 1 Jose @@ -42,8 +46,9 @@ Marketing Job - - + + + John Bruno @@ -54,6 +59,7 @@ More than 5 yrs Experience in PHP + @@ -63,8 +69,9 @@ Send mail regarding our interview - Finance Job - + Finance Manager + + @@ -77,8 +84,9 @@ Trainee - MCA - - + + + Tina Augustie 9898745745 @@ -90,7 +98,8 @@ Programmer - + + Shane Williams 9812398524 @@ -102,7 +111,8 @@ Advertisement - + + David Armstrong @@ -114,7 +124,80 @@ + recruit + 4 + + + + + + + + recruit + 1 + + + + 1 + recruit + + + + recruit + 3 + + + + recruit + 6 + + + + + hr.applicant + + Please do refer to this application for sure. + comment + + + + Regarding reference + hr.applicant + + Hello!
+ I will surely refer to this application as it is by your reference and
+ will try to conduct an interview within a very short time
+ Thanks,

]]>
+ comment + + +
+ + + Refuse Application + hr.applicant + + Hello,

+

I have checked this application but It's not match with our requirement. so no need to process further and we should refuse this application.

+

Kind regards,

]]>
+ comment + + +
+ + hr.applicant + + Hello,

+

We should move further for this application as early as possible..

+

Kind regards,

]]>
+ comment + + +
+
diff --git a/addons/hr_recruitment/hr_recruitment_menu.xml b/addons/hr_recruitment/hr_recruitment_menu.xml index c280f64105c..2dcd490b075 100644 --- a/addons/hr_recruitment/hr_recruitment_menu.xml +++ b/addons/hr_recruitment/hr_recruitment_menu.xml @@ -10,13 +10,15 @@ -

- OpenERP helps you track applicants in the recruitment - process and follow up all operations: meetings, interviews, etc. +

+ Click to add a new job applicant.

- Applicants and their attached CV are created automatically when an email is sent. - If you install the document management modules, all resumes are indexed automatically, - so that you can easily search through their content. + OpenERP helps you track applicants in the recruitment process + and follow up all operations: meetings, interviews, etc. + Candidates and their cv's are automatically created when they + apply for a job. If you install the document management modules, + all resumes are indexed automatically, so that you can easily + search through their content in the recruitment menu.

@@ -61,6 +63,7 @@ name="Applications" parent="base.menu_crm_case_job_req_main" id="menu_crm_case_categ0_act_job" action="crm_case_categ0_act_job" sequence="1"/> + diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index abb90e8f46b..bd90ccc9a45 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -83,6 +83,7 @@