You have not updated your progress for the goal ${object.definition_id.name} (currently reached at ${object.completeness}%) for at least ${object.remind_update_delay} days. Do not forget to do it.
+
+
If you have not changed your score yet, you can use the button "The current value is up to date" to indicate so.
+
+ % endfor
+% endif
+ ]]>
+
+
+
+
+
+
+
+
+ Set your Timezone
+ Configure your profile and specify your timezone
+ count
+ boolean
+
+ [('id','=',user.id),('partner_id.tz', '!=', False)]
+
+ user.id
+
+
+
+ Set your Company Data
+ Write some information about your company (specify at least a name)
+ count
+ boolean
+
+ [('user_ids', 'in', [user.id]), ('name', '=', 'Your Company')]
+ lower
+
+ user.company_id.id
+
+
+
+ Set your Company Logo
+ count
+ boolean
+
+ [('user_ids', 'in', user.id),('logo', '!=', False)]
+
+ user.company_id.id
+
+
+
+ Create User
+ ir.actions.act_window
+ res.users
+ form
+ current
+
+ {'default_groups_ref': ['base.group_user']}
+ Create and manage users that will connect to the system. Users can be deactivated should there be a period of time during which they will/should not connect to the system. You can assign them groups in order to give them specific access to the applications they need to use in the system.
+
+
+
+ Invite new Users
+ Create at least another user
+ boolean
+ count
+
+ [('id', '!=', user.id)]
+
+
+
+
+ Mail Group Following
+ Follow mail groups to receive news
+ python
+ result = pool.get('mail.followers').search(cr, uid, [('res_model', '=', 'mail.group'), ('partner_id', '=', object.user_id.partner_id.id)], count=True, context=context)
+
+
+
+
+
+
+ Complete your Profile
+ once
+ personal
+ never
+
+ inprogress
+ other
+
+
+
+ Setup your Company
+ once
+ personal
+ never
+
+ inprogress
+ other
+
+
+
+
+
+ 1
+
+
+
+
+
+ 1
+
+
+
+
+ 0
+
+
+
+
+ 1
+
+
+
+
+
diff --git a/addons/gamification/html/index.html b/addons/gamification/html/index.html
new file mode 100644
index 00000000000..890efa0d1ec
--- /dev/null
+++ b/addons/gamification/html/index.html
@@ -0,0 +1,86 @@
+
+
+
+
Drive Engagement with Gamification
+
Leverage natural desire for competition
+
+ Reinforce good habits and improve win rates with real-time recognition and rewards inspired by game mechanics. Align teams around clear business objectives with challenges, personal objectives and team leader boards.
+
+
+
Leaderboards
+
+
+
+
+ Promote leaders and competition amongst sales team with performance ratios.
+
+
+
+
Personnal Objectives
+
+
+
+
+ Assign clear goals to users to align them with the company objectives.
+
+
+
+
Visual Information
+
+
+
+
+ See in an glance the progress of each user.
+
+
+
+
+
+
+
+
+
Create custom Challenges
+
+
+Use predefined goals to generate easily your own challenges. Assign it to a team or individual users. Receive feedback as often as needed: daily, weekly... Repeat it automatically to compare progresses through time.
+
+
+
+
+
+
+
+
+
+
+
+
+
Motivate with Badges
+
+
+
+
+
+
+
+Inspire achievement with recognition of coworker's good work by rewarding badges. These can be deserved manually or upon completion of challenges. Add fun to the competition with rare badges.
+
+
+
+
+
+
+
+
Adapt to any module
+
+
+Create goals linked to any module. The evaluation system is very flexible and can be used for many different tasks : sales evaluation, creation of events, project completion or even helping new users to complete their profile.
+
+
+
+
+
+
+
+
+
diff --git a/addons/gamification/models/__init__.py b/addons/gamification/models/__init__.py
new file mode 100644
index 00000000000..812a08b1e19
--- /dev/null
+++ b/addons/gamification/models/__init__.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 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 goal
+import challenge
+import res_users
+import badge
diff --git a/addons/gamification/models/badge.py b/addons/gamification/models/badge.py
new file mode 100644
index 00000000000..1d879393ed2
--- /dev/null
+++ b/addons/gamification/models/badge.py
@@ -0,0 +1,276 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from openerp import SUPERUSER_ID
+from openerp.osv import fields, osv
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
+from openerp.tools.translate import _
+
+from datetime import date
+import logging
+
+_logger = logging.getLogger(__name__)
+
+class gamification_badge_user(osv.Model):
+ """User having received a badge"""
+
+ _name = 'gamification.badge.user'
+ _description = 'Gamification user badge'
+ _order = "create_date desc"
+
+ _columns = {
+ 'user_id': fields.many2one('res.users', string="User", required=True),
+ 'sender_id': fields.many2one('res.users', string="Sender", help="The user who has send the badge"),
+ 'badge_id': fields.many2one('gamification.badge', string='Badge', required=True),
+ 'comment': fields.text('Comment'),
+ 'badge_name': fields.related('badge_id', 'name', type="char", string="Badge Name"),
+ 'create_date': fields.datetime('Created', readonly=True),
+ 'create_uid': fields.many2one('res.users', string='Creator', readonly=True),
+ }
+
+
+ def _send_badge(self, cr, uid, ids, context=None):
+ """Send a notification to a user for receiving a badge
+
+ Does not verify constrains on badge granting.
+ The users are added to the owner_ids (create badge_user if needed)
+ The stats counters are incremented
+ :param ids: list(int) of badge users that will receive the badge
+ """
+ res = True
+ temp_obj = self.pool.get('email.template')
+ user_obj = self.pool.get('res.users')
+ template_id = self.pool['ir.model.data'].get_object(cr, uid, 'gamification', 'email_template_badge_received', context)
+ for badge_user in self.browse(cr, uid, ids, context=context):
+ body_html = temp_obj.render_template(cr, uid, template_id.body_html, 'gamification.badge.user', badge_user.id, context=context)
+ res = user_obj.message_post(cr, uid, badge_user.user_id.id, body=body_html, context=context)
+ return res
+
+ def create(self, cr, uid, vals, context=None):
+ self.pool.get('gamification.badge').check_granting(cr, uid, badge_id=vals.get('badge_id'), context=context)
+ return super(gamification_badge_user, self).create(cr, uid, vals, context=context)
+
+
+class gamification_badge(osv.Model):
+ """Badge object that users can send and receive"""
+
+ CAN_GRANT = 1
+ NOBODY_CAN_GRANT = 2
+ USER_NOT_VIP = 3
+ BADGE_REQUIRED = 4
+ TOO_MANY = 5
+
+ _name = 'gamification.badge'
+ _description = 'Gamification badge'
+ _inherit = ['mail.thread']
+
+ def _get_owners_info(self, cr, uid, ids, name, args, context=None):
+ """Return:
+ the list of unique res.users ids having received this badge
+ the total number of time this badge was granted
+ the total number of users this badge was granted to
+ """
+ result = dict.fromkeys(ids, False)
+ for obj in self.browse(cr, uid, ids, context=context):
+ res = list(set(owner.user_id.id for owner in obj.owner_ids))
+ result[obj.id] = {
+ 'unique_owner_ids': res,
+ 'stat_count': len(obj.owner_ids),
+ 'stat_count_distinct': len(res)
+ }
+ return result
+
+ def _get_badge_user_stats(self, cr, uid, ids, name, args, context=None):
+ """Return stats related to badge users"""
+ result = dict.fromkeys(ids, False)
+ badge_user_obj = self.pool.get('gamification.badge.user')
+ first_month_day = date.today().replace(day=1).strftime(DF)
+ for bid in ids:
+ result[bid] = {
+ 'stat_my': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('user_id', '=', uid)], context=context, count=True),
+ 'stat_this_month': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('create_date', '>=', first_month_day)], context=context, count=True),
+ 'stat_my_this_month': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('user_id', '=', uid), ('create_date', '>=', first_month_day)], context=context, count=True),
+ 'stat_my_monthly_sending': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('create_uid', '=', uid), ('create_date', '>=', first_month_day)], context=context, count=True)
+ }
+ return result
+
+ def _remaining_sending_calc(self, cr, uid, ids, name, args, context=None):
+ """Computes the number of badges remaining the user can send
+
+ 0 if not allowed or no remaining
+ integer if limited sending
+ -1 if infinite (should not be displayed)
+ """
+ result = dict.fromkeys(ids, False)
+ for badge in self.browse(cr, uid, ids, context=context):
+ if self._can_grant_badge(cr, uid, badge.id, context) != 1:
+ # if the user cannot grant this badge at all, result is 0
+ result[badge.id] = 0
+ elif not badge.rule_max:
+ # if there is no limitation, -1 is returned which means 'infinite'
+ result[badge.id] = -1
+ else:
+ result[badge.id] = badge.rule_max_number - badge.stat_my_monthly_sending
+ return result
+
+ _columns = {
+ 'name': fields.char('Badge', required=True, translate=True),
+ 'description': fields.text('Description'),
+ 'image': fields.binary("Image", help="This field holds the image used for the badge, limited to 256x256"),
+ 'rule_auth': fields.selection([
+ ('everyone', 'Everyone'),
+ ('users', 'A selected list of users'),
+ ('having', 'People having some badges'),
+ ('nobody', 'No one, assigned through challenges'),
+ ],
+ string="Allowance to Grant",
+ help="Who can grant this badge",
+ required=True),
+ 'rule_auth_user_ids': fields.many2many('res.users', 'rel_badge_auth_users',
+ string='Authorized Users',
+ help="Only these people can give this badge"),
+ 'rule_auth_badge_ids': fields.many2many('gamification.badge',
+ 'rel_badge_badge', 'badge1_id', 'badge2_id',
+ string='Required Badges',
+ help="Only the people having these badges can give this badge"),
+
+ 'rule_max': fields.boolean('Monthly Limited Sending',
+ help="Check to set a monthly limit per person of sending this badge"),
+ 'rule_max_number': fields.integer('Limitation Number',
+ help="The maximum number of time this badge can be sent per month per person."),
+ 'stat_my_monthly_sending': fields.function(_get_badge_user_stats,
+ type="integer",
+ string='My Monthly Sending Total',
+ multi='badge_users',
+ help="The number of time the current user has sent this badge this month."),
+ 'remaining_sending': fields.function(_remaining_sending_calc, type='integer',
+ string='Remaining Sending Allowed', help="If a maxium is set"),
+
+ 'challenge_ids': fields.one2many('gamification.challenge', 'reward_id',
+ string="Reward of Challenges"),
+
+ 'goal_definition_ids': fields.many2many('gamification.goal.definition', 'badge_unlocked_definition_rel',
+ string='Rewarded by',
+ help="The users that have succeeded theses goals will receive automatically the badge."),
+
+ 'owner_ids': fields.one2many('gamification.badge.user', 'badge_id',
+ string='Owners', help='The list of instances of this badge granted to users'),
+ 'active': fields.boolean('Active'),
+ 'unique_owner_ids': fields.function(_get_owners_info,
+ string='Unique Owners',
+ help="The list of unique users having received this badge.",
+ multi='unique_users',
+ type="many2many", relation="res.users"),
+
+ 'stat_count': fields.function(_get_owners_info, string='Total',
+ type="integer",
+ multi='unique_users',
+ help="The number of time this badge has been received."),
+ 'stat_count_distinct': fields.function(_get_owners_info,
+ type="integer",
+ string='Number of users',
+ multi='unique_users',
+ help="The number of time this badge has been received by unique users."),
+ 'stat_this_month': fields.function(_get_badge_user_stats,
+ type="integer",
+ string='Monthly total',
+ multi='badge_users',
+ help="The number of time this badge has been received this month."),
+ 'stat_my': fields.function(_get_badge_user_stats, string='My Total',
+ type="integer",
+ multi='badge_users',
+ help="The number of time the current user has received this badge."),
+ 'stat_my_this_month': fields.function(_get_badge_user_stats,
+ type="integer",
+ string='My Monthly Total',
+ multi='badge_users',
+ help="The number of time the current user has received this badge this month."),
+ }
+
+ _defaults = {
+ 'rule_auth': 'everyone',
+ 'active': True,
+ }
+
+ def check_granting(self, cr, uid, badge_id, context=None):
+ """Check the user 'uid' can grant the badge 'badge_id' and raise the appropriate exception
+ if not
+
+ Do not check for SUPERUSER_ID
+ """
+ status_code = self._can_grant_badge(cr, uid, badge_id, context=context)
+ if status_code == self.CAN_GRANT:
+ return True
+ elif status_code == self.NOBODY_CAN_GRANT:
+ raise osv.except_osv(_('Warning!'), _('This badge can not be sent by users.'))
+ elif status_code == self.USER_NOT_VIP:
+ raise osv.except_osv(_('Warning!'), _('You are not in the user allowed list.'))
+ elif status_code == self.BADGE_REQUIRED:
+ raise osv.except_osv(_('Warning!'), _('You do not have the required badges.'))
+ elif status_code == self.TOO_MANY:
+ raise osv.except_osv(_('Warning!'), _('You have already sent this badge too many time this month.'))
+ else:
+ _logger.exception("Unknown badge status code: %d" % int(status_code))
+ return False
+
+ def _can_grant_badge(self, cr, uid, badge_id, context=None):
+ """Check if a user can grant a badge to another user
+
+ :param uid: the id of the res.users trying to send the badge
+ :param badge_id: the granted badge id
+ :return: integer representing the permission.
+ """
+ if uid == SUPERUSER_ID:
+ return self.CAN_GRANT
+
+ badge = self.browse(cr, uid, badge_id, context=context)
+
+ if badge.rule_auth == 'nobody':
+ return self.NOBODY_CAN_GRANT
+
+ elif badge.rule_auth == 'users' and uid not in [user.id for user in badge.rule_auth_user_ids]:
+ return self.USER_NOT_VIP
+
+ elif badge.rule_auth == 'having':
+ all_user_badges = self.pool.get('gamification.badge.user').search(cr, uid, [('user_id', '=', uid)], context=context)
+ for required_badge in badge.rule_auth_badge_ids:
+ if required_badge.id not in all_user_badges:
+ return self.BADGE_REQUIRED
+
+ if badge.rule_max and badge.stat_my_monthly_sending >= badge.rule_max_number:
+ return self.TOO_MANY
+
+ # badge.rule_auth == 'everyone' -> no check
+ return self.CAN_GRANT
+
+ def check_progress(self, cr, uid, context=None):
+ try:
+ model, res_id = template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'badge_hidden')
+ except ValueError:
+ return True
+ badge_user_obj = self.pool.get('gamification.badge.user')
+ if not badge_user_obj.search(cr, uid, [('user_id', '=', uid), ('badge_id', '=', res_id)], context=context):
+ values = {
+ 'user_id': uid,
+ 'badge_id': res_id,
+ }
+ badge_user_obj.create(cr, SUPERUSER_ID, values, context=context)
+ return True
\ No newline at end of file
diff --git a/addons/gamification/models/challenge.py b/addons/gamification/models/challenge.py
new file mode 100644
index 00000000000..ae27aa96719
--- /dev/null
+++ b/addons/gamification/models/challenge.py
@@ -0,0 +1,821 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from openerp import SUPERUSER_ID
+from openerp.osv import fields, osv
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
+from openerp.tools.translate import _
+
+from datetime import date, datetime, timedelta
+import calendar
+import logging
+_logger = logging.getLogger(__name__)
+
+# display top 3 in ranking, could be db variable
+MAX_VISIBILITY_RANKING = 3
+
+def start_end_date_for_period(period, default_start_date=False, default_end_date=False):
+ """Return the start and end date for a goal period based on today
+
+ :return: (start_date, end_date), datetime.date objects, False if the period is
+ not defined or unknown"""
+ today = date.today()
+ if period == 'daily':
+ start_date = today
+ end_date = start_date
+ elif period == 'weekly':
+ delta = timedelta(days=today.weekday())
+ start_date = today - delta
+ end_date = start_date + timedelta(days=7)
+ elif period == 'monthly':
+ month_range = calendar.monthrange(today.year, today.month)
+ start_date = today.replace(day=1)
+ end_date = today.replace(day=month_range[1])
+ elif period == 'yearly':
+ start_date = today.replace(month=1, day=1)
+ end_date = today.replace(month=12, day=31)
+ else: # period == 'once':
+ start_date = default_start_date # for manual goal, start each time
+ end_date = default_end_date
+
+ if start_date and end_date:
+ return (start_date.strftime(DF), end_date.strftime(DF))
+ else:
+ return (start_date, end_date)
+
+
+class gamification_challenge(osv.Model):
+ """Gamification challenge
+
+ Set of predifined objectives assigned to people with rules for recurrence and
+ rewards
+
+ If 'user_ids' is defined and 'period' is different than 'one', the set will
+ be assigned to the users for each period (eg: every 1st of each month if
+ 'monthly' is selected)
+ """
+
+ _name = 'gamification.challenge'
+ _description = 'Gamification challenge'
+ _inherit = 'mail.thread'
+
+ def _get_next_report_date(self, cr, uid, ids, field_name, arg, context=None):
+ """Return the next report date based on the last report date and report
+ period.
+
+ :return: a string in DEFAULT_SERVER_DATE_FORMAT representing the date"""
+ res = {}
+ for challenge in self.browse(cr, uid, ids, context):
+ last = datetime.strptime(challenge.last_report_date, DF).date()
+ if challenge.report_message_frequency == 'daily':
+ next = last + timedelta(days=1)
+ res[challenge.id] = next.strftime(DF)
+ elif challenge.report_message_frequency == 'weekly':
+ next = last + timedelta(days=7)
+ res[challenge.id] = next.strftime(DF)
+ elif challenge.report_message_frequency == 'monthly':
+ month_range = calendar.monthrange(last.year, last.month)
+ next = last.replace(day=month_range[1]) + timedelta(days=1)
+ res[challenge.id] = next.strftime(DF)
+ elif challenge.report_message_frequency == 'yearly':
+ res[challenge.id] = last.replace(year=last.year + 1).strftime(DF)
+ # frequency == 'once', reported when closed only
+ else:
+ res[challenge.id] = False
+
+ return res
+
+ def _get_categories(self, cr, uid, context=None):
+ return [
+ ('hr', 'Human Ressources / Engagement'),
+ ('other', 'Settings / Gamification Tools'),
+ ]
+
+ def _get_report_template(self, cr, uid, context=None):
+ try:
+ return self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'simple_report_template')[1]
+ except ValueError:
+ return False
+
+ _order = 'end_date, start_date, name, id'
+ _columns = {
+ 'name': fields.char('Challenge Name', required=True, translate=True),
+ 'description': fields.text('Description', translate=True),
+ 'state': fields.selection([
+ ('draft', 'Draft'),
+ ('inprogress', 'In Progress'),
+ ('done', 'Done'),
+ ],
+ string='State', required=True, track_visibility='onchange'),
+ 'manager_id': fields.many2one('res.users',
+ string='Responsible', help="The user responsible for the challenge."),
+
+ 'user_ids': fields.many2many('res.users', 'user_ids',
+ string='Users',
+ help="List of users participating to the challenge"),
+ 'autojoin_group_id': fields.many2one('res.groups',
+ string='Auto-subscription Group',
+ help='Group of users whose members will be automatically added to user_ids once the challenge is started'),
+
+ 'period': fields.selection([
+ ('once', 'Non recurring'),
+ ('daily', 'Daily'),
+ ('weekly', 'Weekly'),
+ ('monthly', 'Monthly'),
+ ('yearly', 'Yearly')
+ ],
+ string='Periodicity',
+ help='Period of automatic goal assigment. If none is selected, should be launched manually.',
+ required=True),
+ 'start_date': fields.date('Start Date',
+ help="The day a new challenge will be automatically started. If no periodicity is set, will use this date as the goal start date."),
+ 'end_date': fields.date('End Date',
+ help="The day a new challenge will be automatically closed. If no periodicity is set, will use this date as the goal end date."),
+
+ 'invited_user_ids': fields.many2many('res.users', 'invited_user_ids',
+ string="Suggest to users"),
+
+ 'line_ids': fields.one2many('gamification.challenge.line', 'challenge_id',
+ string='Lines',
+ help="List of goals that will be set",
+ required=True),
+
+ 'reward_id': fields.many2one('gamification.badge', string="For Every Succeding User"),
+ 'reward_first_id': fields.many2one('gamification.badge', string="For 1st user"),
+ 'reward_second_id': fields.many2one('gamification.badge', string="For 2nd user"),
+ 'reward_third_id': fields.many2one('gamification.badge', string="For 3rd user"),
+ 'reward_failure': fields.boolean('Reward Bests if not Succeeded?'),
+
+ 'visibility_mode': fields.selection([
+ ('personal', 'Individual Goals'),
+ ('ranking', 'Leader Board (Group Ranking)'),
+ ],
+ string="Display Mode", required=True),
+
+ 'report_message_frequency': fields.selection([
+ ('never', 'Never'),
+ ('onchange', 'On change'),
+ ('daily', 'Daily'),
+ ('weekly', 'Weekly'),
+ ('monthly', 'Monthly'),
+ ('yearly', 'Yearly')
+ ],
+ string="Report Frequency", required=True),
+ 'report_message_group_id': fields.many2one('mail.group',
+ string='Send a copy to',
+ help='Group that will receive a copy of the report in addition to the user'),
+ 'report_template_id': fields.many2one('email.template', string="Report Template", required=True),
+ 'remind_update_delay': fields.integer('Non-updated manual goals will be reminded after',
+ help="Never reminded if no value or zero is specified."),
+ 'last_report_date': fields.date('Last Report Date'),
+ 'next_report_date': fields.function(_get_next_report_date,
+ type='date', string='Next Report Date', store=True),
+
+ 'category': fields.selection(lambda s, *a, **k: s._get_categories(*a, **k),
+ string="Appears in", help="Define the visibility of the challenge through menus", required=True),
+ }
+
+ _defaults = {
+ 'period': 'once',
+ 'state': 'draft',
+ 'visibility_mode': 'personal',
+ 'report_message_frequency': 'never',
+ 'last_report_date': fields.date.today,
+ 'start_date': fields.date.today,
+ 'manager_id': lambda s, cr, uid, c: uid,
+ 'category': 'hr',
+ 'reward_failure': False,
+ 'report_template_id': lambda s, *a, **k: s._get_report_template(*a, **k),
+ }
+
+
+ def create(self, cr, uid, vals, context=None):
+ """Overwrite the create method to add the user of groups"""
+
+ # add users when change the group auto-subscription
+ if vals.get('autojoin_group_id'):
+ new_group = self.pool.get('res.groups').browse(cr, uid, vals['autojoin_group_id'], context=context)
+
+ if not vals.get('user_ids'):
+ vals['user_ids'] = []
+ vals['user_ids'] += [(4, user.id) for user in new_group.users]
+
+ create_res = super(gamification_challenge, self).create(cr, uid, vals, context=context)
+
+ # subscribe new users to the challenge
+ if vals.get('user_ids'):
+ # done with browse after super to be sure catch all after orm process
+ challenge = self.browse(cr, uid, create_res, context=context)
+ self.message_subscribe_users(cr, uid, [challenge.id], [user.id for user in challenge.user_ids], context=context)
+
+ return create_res
+
+ def write(self, cr, uid, ids, vals, context=None):
+ if isinstance(ids, (int,long)):
+ ids = [ids]
+
+ # add users when change the group auto-subscription
+ if vals.get('autojoin_group_id'):
+ new_group = self.pool.get('res.groups').browse(cr, uid, vals['autojoin_group_id'], context=context)
+
+ if not vals.get('user_ids'):
+ vals['user_ids'] = []
+ vals['user_ids'] += [(4, user.id) for user in new_group.users]
+
+ if vals.get('state') == 'inprogress':
+ # starting a challenge
+ if not vals.get('autojoin_group_id'):
+ # starting challenge, add users in autojoin group
+ if not vals.get('user_ids'):
+ vals['user_ids'] = []
+ for challenge in self.browse(cr, uid, ids, context=context):
+ if challenge.autojoin_group_id:
+ vals['user_ids'] += [(4, user.id) for user in challenge.autojoin_group_id.users]
+
+ self.generate_goals_from_challenge(cr, uid, ids, context=context)
+
+ elif vals.get('state') == 'done':
+ self.check_challenge_reward(cr, uid, ids, force=True, context=context)
+
+ elif vals.get('state') == 'draft':
+ # resetting progress
+ if self.pool.get('gamification.goal').search(cr, uid, [('challenge_id', 'in', ids), ('state', 'in', ['inprogress', 'inprogress_update'])], context=context):
+ raise osv.except_osv("Error", "You can not reset a challenge with unfinished goals.")
+
+ write_res = super(gamification_challenge, self).write(cr, uid, ids, vals, context=context)
+
+ # subscribe new users to the challenge
+ if vals.get('user_ids'):
+ # done with browse after super if changes in groups
+ for challenge in self.browse(cr, uid, ids, context=context):
+ self.message_subscribe_users(cr, uid, [challenge.id], [user.id for user in challenge.user_ids], context=context)
+
+ return write_res
+
+
+ ##### Update #####
+
+ def _cron_update(self, cr, uid, context=None, ids=False):
+ """Daily cron check.
+
+ - Start planned challenges (in draft and with start_date = today)
+ - Create the missing goals (eg: modified the challenge to add lines)
+ - Update every running challenge
+ """
+ # start planned challenges
+ planned_challenge_ids = self.search(cr, uid, [
+ ('state', '=', 'draft'),
+ ('start_date', '<=', fields.date.today())])
+ self.write(cr, uid, planned_challenge_ids, {'state': 'inprogress'}, context=context)
+
+ # close planned challenges
+ planned_challenge_ids = self.search(cr, uid, [
+ ('state', '=', 'inprogress'),
+ ('end_date', '>=', fields.date.today())])
+ self.write(cr, uid, planned_challenge_ids, {'state': 'done'}, context=context)
+
+ if not ids:
+ ids = self.search(cr, uid, [('state', '=', 'inprogress')], context=context)
+
+ return self._update_all(cr, uid, ids, context=context)
+
+ def _update_all(self, cr, uid, ids, context=None):
+ """Update the challenges and related goals
+
+ :param list(int) ids: the ids of the challenges to update, if False will
+ update only challenges in progress."""
+ if isinstance(ids, (int,long)):
+ ids = [ids]
+
+ goal_obj = self.pool.get('gamification.goal')
+
+ # we use yesterday to update the goals that just ended
+ yesterday = date.today() - timedelta(days=1)
+ goal_ids = goal_obj.search(cr, uid, [
+ ('challenge_id', 'in', ids),
+ '|',
+ ('state', 'in', ('inprogress', 'inprogress_update')),
+ '&',
+ ('state', 'in', ('reached', 'failed')),
+ '|',
+ ('end_date', '>=', yesterday.strftime(DF)),
+ ('end_date', '=', False)
+ ], context=context)
+ # update every running goal already generated linked to selected challenges
+ goal_obj.update(cr, uid, goal_ids, context=context)
+
+ for challenge in self.browse(cr, uid, ids, context=context):
+ if challenge.autojoin_group_id:
+ # check in case of new users in challenge, this happens if manager removed users in challenge manually
+ self.write(cr, uid, [challenge.id], {'user_ids': [(4, user.id) for user in challenge.autojoin_group_id.users]}, context=context)
+ self.generate_goals_from_challenge(cr, uid, [challenge.id], context=context)
+
+ # goals closed but still opened at the last report date
+ closed_goals_to_report = goal_obj.search(cr, uid, [
+ ('challenge_id', '=', challenge.id),
+ ('start_date', '>=', challenge.last_report_date),
+ ('end_date', '<=', challenge.last_report_date)
+ ])
+
+ if len(closed_goals_to_report) > 0:
+ # some goals need a final report
+ self.report_progress(cr, uid, challenge, subset_goal_ids=closed_goals_to_report, context=context)
+
+ if fields.date.today() == challenge.next_report_date:
+ self.report_progress(cr, uid, challenge, context=context)
+
+ self.check_challenge_reward(cr, uid, ids, context=context)
+ return True
+
+ def quick_update(self, cr, uid, challenge_id, context=None):
+ """Update all the goals of a challenge, no generation of new goals"""
+ goal_ids = self.pool.get('gamification.goal').search(cr, uid, [('challenge_id', '=', challenge_id)], context=context)
+ self.pool.get('gamification.goal').update(cr, uid, goal_ids, context=context)
+ return True
+
+
+ def action_check(self, cr, uid, ids, context=None):
+ """Check a challenge
+
+ Create goals that haven't been created yet (eg: if added users)
+ Recompute the current value for each goal related"""
+ return self._update_all(cr, uid, ids=ids, context=context)
+
+ def action_report_progress(self, cr, uid, ids, context=None):
+ """Manual report of a goal, does not influence automatic report frequency"""
+ if isinstance(ids, (int,long)):
+ ids = [ids]
+ for challenge in self.browse(cr, uid, ids, context):
+ self.report_progress(cr, uid, challenge, context=context)
+ return True
+
+
+ ##### Automatic actions #####
+
+ def generate_goals_from_challenge(self, cr, uid, ids, context=None):
+ """Generate the goals for each line and user.
+
+ If goals already exist for this line and user, the line is skipped. This
+ can be called after each change in the list of users or lines.
+ :param list(int) ids: the list of challenge concerned"""
+
+ for challenge in self.browse(cr, uid, ids, context):
+ (start_date, end_date) = start_end_date_for_period(challenge.period)
+
+ # if no periodicity, use challenge dates
+ if not start_date and challenge.start_date:
+ start_date = challenge.start_date
+ if not end_date and challenge.end_date:
+ end_date = challenge.end_date
+
+ for line in challenge.line_ids:
+ for user in challenge.user_ids:
+
+ goal_obj = self.pool.get('gamification.goal')
+ domain = [('line_id', '=', line.id), ('user_id', '=', user.id)]
+ if start_date:
+ domain.append(('start_date', '=', start_date))
+
+ # goal already existing for this line ?
+ if len(goal_obj.search(cr, uid, domain, context=context)) > 0:
+
+ # resume canceled goals
+ domain.append(('state', '=', 'canceled'))
+ canceled_goal_ids = goal_obj.search(cr, uid, domain, context=context)
+ goal_obj.write(cr, uid, canceled_goal_ids, {'state': 'inprogress'}, context=context)
+ goal_obj.update(cr, uid, canceled_goal_ids, context=context)
+
+ # skip to next user
+ continue
+
+ values = {
+ 'definition_id': line.definition_id.id,
+ 'line_id': line.id,
+ 'user_id': user.id,
+ 'target_goal': line.target_goal,
+ 'state': 'inprogress',
+ }
+
+ if start_date:
+ values['start_date'] = start_date
+ if end_date:
+ values['end_date'] = end_date
+
+ if challenge.remind_update_delay:
+ values['remind_update_delay'] = challenge.remind_update_delay
+
+ new_goal_id = goal_obj.create(cr, uid, values, context)
+
+ goal_obj.update(cr, uid, [new_goal_id], context=context)
+
+ return True
+
+ ##### JS utilities #####
+
+ def _get_serialized_challenge_lines(self, cr, uid, challenge, user_id=False, restrict_goal_ids=False, restrict_top=False, context=None):
+ """Return a serialised version of the goals information
+
+ :challenge: browse record of challenge to compute
+ :user_id: res.users id of the user retrieving progress (False if no distinction, only for ranking challenges)
+ :restrict_goal_ids: compute only the results for this subset if gamification.goal ids, if False retrieve every goal of current running challenge
+ :restrict_top: for challenge lines where visibility_mode == 'ranking', retrieve only these bests results and itself, if False retrieve all
+ restrict_goal_ids has priority over restrict_top
+
+ format list
+ # if visibility_mode == 'ranking'
+ {
+ 'name': ,
+ 'description': ,
+ 'condition': ,
+ 'computation_mode': ,
+ 'monetary': <{True,False}>,
+ 'suffix': ,
+ 'action': <{True,False}>,
+ 'display_mode': <{progress,boolean}>,
+ 'target': ,
+ 'own_goal_id': ,
+ 'goals': [
+ {
+ 'id': ,
+ 'rank': ,
+ 'user_id': ,
+ 'name': ,
+ 'state': ,
+ 'completeness': ,
+ 'current': ,
+ }
+ ]
+ },
+ # if visibility_mode == 'personal'
+ {
+ 'id': ,
+ 'name': ,
+ 'description': ,
+ 'condition': ,
+ 'computation_mode': ,
+ 'monetary': <{True,False}>,
+ 'suffix': ,
+ 'action': <{True,False}>,
+ 'display_mode': <{progress,boolean}>,
+ 'target': ,
+ 'state': ,
+ 'completeness': ,
+ 'current': ,
+ }
+ """
+ goal_obj = self.pool.get('gamification.goal')
+ (start_date, end_date) = start_end_date_for_period(challenge.period)
+
+ res_lines = []
+ for line in challenge.line_ids:
+ line_data = {
+ 'name': line.definition_id.name,
+ 'description': line.definition_id.description,
+ 'condition': line.definition_id.condition,
+ 'computation_mode': line.definition_id.computation_mode,
+ 'monetary': line.definition_id.monetary,
+ 'suffix': line.definition_id.suffix,
+ 'action': True if line.definition_id.action_id else False,
+ 'display_mode': line.definition_id.display_mode,
+ 'target': line.target_goal,
+ }
+ domain = [
+ ('line_id', '=', line.id),
+ ('state', '!=', 'draft'),
+ ]
+ if restrict_goal_ids:
+ domain.append(('ids', 'in', restrict_goal_ids))
+ else:
+ # if no subset goals, use the dates for restriction
+ if start_date:
+ domain.append(('start_date', '=', start_date))
+ if end_date:
+ domain.append(('end_date', '=', end_date))
+
+ if challenge.visibility_mode == 'personal':
+ if not user_id:
+ raise osv.except_osv(_('Error!'),_("Retrieving progress for personal challenge without user information"))
+ domain.append(('user_id', '=', user_id))
+ sorting = goal_obj._order
+ limit = 1
+ # initialise in case search returns no results
+ line_data.update({
+ 'id': 0,
+ 'current': 0,
+ 'completeness': 0,
+ 'state': 'draft',
+ })
+ else:
+ line_data.update({
+ 'own_goal_id': False,
+ 'goals': [],
+ })
+ sorting = "completeness desc, current desc"
+ limit = False
+
+ goal_ids = goal_obj.search(cr, uid, domain, order=sorting, limit=limit, context=context)
+ ranking = 0
+ for goal in goal_obj.browse(cr, uid, goal_ids, context=context):
+ if challenge.visibility_mode == 'personal':
+ # limit=1 so only one result
+ line_data.update({
+ 'id': goal.id,
+ 'current': goal.current,
+ 'completeness': goal.completeness,
+ 'state': goal.state,
+ })
+ else:
+ ranking += 1
+ if user_id and goal.user_id.id == user_id:
+ line_data['own_goal_id'] = goal.id
+ elif restrict_top and ranking > restrict_top:
+ # not own goal, over top, skipping
+ continue
+
+ line_data['goals'].append({
+ 'id': goal.id,
+ 'user_id': goal.user_id.id,
+ 'name': goal.user_id.name,
+ 'rank': ranking,
+ 'current': goal.current,
+ 'completeness': goal.completeness,
+ 'state': goal.state,
+ })
+ res_lines.append(line_data)
+ return res_lines
+
+ ##### Reporting #####
+
+ def report_progress(self, cr, uid, challenge, context=None, users=False, subset_goal_ids=False):
+ """Post report about the progress of the goals
+
+ :param challenge: the challenge object that need to be reported
+ :param users: the list(res.users) of users that are concerned by
+ the report. If False, will send the report to every user concerned
+ (goal users and group that receive a copy). Only used for challenge with
+ a visibility mode set to 'personal'.
+ :param goal_ids: the list(int) of goal ids linked to the challenge for
+ the report. If not specified, use the goals for the current challenge
+ period. This parameter can be used to produce report for previous challenge
+ periods.
+ :param subset_goal_ids: a list(int) of goal ids to restrict the report
+ """
+ if context is None:
+ context = {}
+
+ temp_obj = self.pool.get('email.template')
+ ctx = context.copy()
+ if challenge.visibility_mode == 'ranking':
+ lines_boards = self._get_serialized_challenge_lines(cr, uid, challenge, user_id=False, restrict_goal_ids=subset_goal_ids, restrict_top=False, context=context)
+
+ ctx.update({'challenge_lines': lines_boards})
+ body_html = temp_obj.render_template(cr, uid, challenge.report_template_id.body_html, 'gamification.challenge', challenge.id, context=ctx)
+
+ # send to every follower of the challenge
+ self.message_post(cr, uid, challenge.id,
+ body=body_html,
+ context=context,
+ subtype='mail.mt_comment')
+ if challenge.report_message_group_id:
+ self.pool.get('mail.group').message_post(cr, uid, challenge.report_message_group_id.id,
+ body=body_html,
+ context=context,
+ subtype='mail.mt_comment')
+
+ else:
+ # generate individual reports
+ for user in users or challenge.user_ids:
+ goals = self._get_serialized_challenge_lines(cr, uid, challenge, user.id, restrict_goal_ids=subset_goal_ids, context=context)
+ if not goals:
+ continue
+
+ ctx.update({'challenge_lines': goals})
+ body_html = temp_obj.render_template(cr, user.id, challenge.report_template_id.body_html, 'gamification.challenge', challenge.id, context=ctx)
+
+ # send message only to users, not on the challenge
+ self.message_post(cr, uid, 0,
+ body=body_html,
+ partner_ids=[(4, user.partner_id.id)],
+ context=context,
+ subtype='mail.mt_comment')
+ if challenge.report_message_group_id:
+ self.pool.get('mail.group').message_post(cr, uid, challenge.report_message_group_id.id,
+ body=body_html,
+ context=context,
+ subtype='mail.mt_comment')
+ return self.write(cr, uid, challenge.id, {'last_report_date': fields.date.today()}, context=context)
+
+ ##### Challenges #####
+ def accept_challenge(self, cr, uid, challenge_ids, context=None, user_id=None):
+ """The user accept the suggested challenge"""
+ user_id = user_id or uid
+ user = self.pool.get('res.users').browse(cr, uid, user_id, context=context)
+ message = "%s has joined the challenge" % user.name
+ self.message_post(cr, uid, challenge_ids, body=message, context=context)
+ self.write(cr, SUPERUSER_ID, challenge_ids, {'invited_user_ids': [(3, user_id)], 'user_ids': [(4, user_id)]}, context=context)
+ return self.generate_goals_from_challenge(cr, uid, challenge_ids, context=context)
+
+ def discard_challenge(self, cr, uid, challenge_ids, context=None, user_id=None):
+ """The user discard the suggested challenge"""
+ user_id = user_id or uid
+ user = self.pool.get('res.users').browse(cr, uid, user_id, context=context)
+ message = "%s has refused the challenge" % user.name
+ self.message_post(cr, SUPERUSER_ID, challenge_ids, body=message, context=context)
+ return self.write(cr, uid, challenge_ids, {'invited_user_ids': (3, user_id)}, context=context)
+
+ def reply_challenge_wizard(self, cr, uid, challenge_id, context=None):
+ result = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'challenge_wizard')
+ id = result and result[1] or False
+ result = self.pool.get('ir.actions.act_window').read(cr, uid, [id], context=context)[0]
+ result['res_id'] = challenge_id
+ return result
+
+ def check_challenge_reward(self, cr, uid, ids, force=False, context=None):
+ """Actions for the end of a challenge
+
+ If a reward was selected, grant it to the correct users.
+ Rewards granted at:
+ - the end date for a challenge with no periodicity
+ - the end of a period for challenge with periodicity
+ - when a challenge is manually closed
+ (if no end date, a running challenge is never rewarded)
+ """
+ if isinstance(ids, (int,long)):
+ ids = [ids]
+ context = context or {}
+ for challenge in self.browse(cr, uid, ids, context=context):
+ (start_date, end_date) = start_end_date_for_period(challenge.period, challenge.start_date, challenge.end_date)
+ yesterday = date.today() - timedelta(days=1)
+ if end_date == yesterday.strftime(DF) or force:
+ # open chatter message
+ message_body = _("The challenge %s is finished." % challenge.name)
+
+ # reward for everybody succeeding
+ rewarded_users = []
+ if challenge.reward_id:
+ for user in challenge.user_ids:
+ reached_goal_ids = self.pool.get('gamification.goal').search(cr, uid, [
+ ('challenge_id', '=', challenge.id),
+ ('user_id', '=', user.id),
+ ('start_date', '=', start_date),
+ ('end_date', '=', end_date),
+ ('state', '=', 'reached')
+ ], context=context)
+ if len(reached_goal_ids) == len(challenge.line_ids):
+ self.reward_user(cr, uid, user.id, challenge.reward_id.id, context)
+ rewarded_users.append(user)
+
+ if rewarded_users:
+ message_body += _(" Reward (badge %s) for every succeeding user was sent to %s." % (challenge.reward_id.name, ", ".join([user.name for user in rewarded_users])))
+ else:
+ message_body += _(" Nobody has succeeded to reach every goal, no badge is rewared for this challenge.")
+
+ # reward bests
+ if challenge.reward_first_id:
+ (first_user, second_user, third_user) = self.get_top3_users(cr, uid, challenge, context)
+ if first_user:
+ self.reward_user(cr, uid, first_user.id, challenge.reward_first_id.id, context)
+ message_body += _(" Special rewards were sent to the top competing users. The ranking for this challenge is :")
+ message_body += " 1. %s - %s" % (first_user.name, challenge.reward_first_id.name)
+ else:
+ message_body += _("Nobody reached the required conditions to receive special badges.")
+
+ if second_user and challenge.reward_second_id:
+ self.reward_user(cr, uid, second_user.id, challenge.reward_second_id.id, context)
+ message_body += " 2. %s - %s" % (second_user.name, challenge.reward_second_id.name)
+ if third_user and challenge.reward_third_id:
+ self.reward_user(cr, uid, third_user.id, challenge.reward_second_id.id, context)
+ message_body += " 3. %s - %s" % (third_user.name, challenge.reward_third_id.name)
+
+ self.message_post(cr, uid, challenge.id, body=message_body, context=context)
+ return True
+
+ def get_top3_users(self, cr, uid, challenge, context=None):
+ """Get the top 3 users for a defined challenge
+
+ Ranking criterias:
+ 1. succeed every goal of the challenge
+ 2. total completeness of each goal (can be over 100)
+ Top 3 is computed only for users succeeding every goal of the challenge,
+ except if reward_failure is True, in which case every user is
+ considered.
+ :return: ('first', 'second', 'third'), tuple containing the res.users
+ objects of the top 3 users. If no user meets the criterias for a rank,
+ it is set to False. Nobody can receive a rank is noone receives the
+ higher one (eg: if 'second' == False, 'third' will be False)
+ """
+ goal_obj = self.pool.get('gamification.goal')
+ (start_date, end_date) = start_end_date_for_period(challenge.period, challenge.start_date, challenge.end_date)
+ challengers = []
+ for user in challenge.user_ids:
+ all_reached = True
+ total_completness = 0
+ # every goal of the user for the running period
+ goal_ids = goal_obj.search(cr, uid, [
+ ('challenge_id', '=', challenge.id),
+ ('user_id', '=', user.id),
+ ('start_date', '=', start_date),
+ ('end_date', '=', end_date)
+ ], context=context)
+ for goal in goal_obj.browse(cr, uid, goal_ids, context=context):
+ if goal.state != 'reached':
+ all_reached = False
+ if goal.definition_condition == 'higher':
+ # can be over 100
+ total_completness += 100.0 * goal.current / goal.target_goal
+ elif goal.state == 'reached':
+ # for lower goals, can not get percentage so 0 or 100
+ total_completness += 100
+
+ challengers.append({'user': user, 'all_reached': all_reached, 'total_completness': total_completness})
+ sorted_challengers = sorted(challengers, key=lambda k: (k['all_reached'], k['total_completness']), reverse=True)
+
+ if len(sorted_challengers) == 0 or (not challenge.reward_failure and not sorted_challengers[0]['all_reached']):
+ # nobody succeeded
+ return (False, False, False)
+ if len(sorted_challengers) == 1 or (not challenge.reward_failure and not sorted_challengers[1]['all_reached']):
+ # only one user succeeded
+ return (sorted_challengers[0]['user'], False, False)
+ if len(sorted_challengers) == 2 or (not challenge.reward_failure and not sorted_challengers[2]['all_reached']):
+ # only one user succeeded
+ return (sorted_challengers[0]['user'], sorted_challengers[1]['user'], False)
+ return (sorted_challengers[0]['user'], sorted_challengers[1]['user'], sorted_challengers[2]['user'])
+
+ def reward_user(self, cr, uid, user_id, badge_id, context=None):
+ """Create a badge user and send the badge to him
+
+ :param user_id: the user to reward
+ :param badge_id: the concerned badge
+ """
+ badge_user_obj = self.pool.get('gamification.badge.user')
+ user_badge_id = badge_user_obj.create(cr, uid, {'user_id': user_id, 'badge_id': badge_id}, context=context)
+ return badge_user_obj._send_badge(cr, uid, [user_badge_id], context=context)
+
+
+class gamification_challenge_line(osv.Model):
+ """Gamification challenge line
+
+ Predifined goal for 'gamification_challenge'
+ These are generic list of goals with only the target goal defined
+ Should only be created for the gamification_challenge object
+ """
+
+ _name = 'gamification.challenge.line'
+ _description = 'Gamification generic goal for challenge'
+ _order = "sequence, id"
+
+ def on_change_definition_id(self, cr, uid, ids, definition_id=False, context=None):
+ goal_definition = self.pool.get('gamification.goal.definition')
+ if not definition_id:
+ return {'value': {'definition_id': False}}
+ goal_definition = goal_definition.browse(cr, uid, definition_id, context=context)
+ ret = {
+ 'value': {
+ 'condition': goal_definition.condition,
+ 'definition_full_suffix': goal_definition.full_suffix
+ }
+ }
+ return ret
+
+ _columns = {
+ 'name': fields.related('definition_id', 'name', string="Name"),
+ 'challenge_id': fields.many2one('gamification.challenge',
+ string='Challenge',
+ required=True,
+ ondelete="cascade"),
+ 'definition_id': fields.many2one('gamification.goal.definition',
+ string='Goal Definition',
+ required=True,
+ ondelete="cascade"),
+ 'target_goal': fields.float('Target Value to Reach',
+ required=True),
+ 'sequence': fields.integer('Sequence',
+ help='Sequence number for ordering'),
+ 'condition': fields.related('definition_id', 'condition', type="selection",
+ readonly=True, string="Condition", selection=[('lower', '<='), ('higher', '>=')]),
+ 'definition_suffix': fields.related('definition_id', 'suffix', type="char", readonly=True, string="Unit"),
+ 'definition_monetary': fields.related('definition_id', 'monetary', type="boolean", readonly=True, string="Monetary"),
+ 'definition_full_suffix': fields.related('definition_id', 'full_suffix', type="char", readonly=True, string="Suffix"),
+ }
+
+ _default = {
+ 'sequence': 1,
+ }
diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/goal.py
new file mode 100644
index 00000000000..8792e219d71
--- /dev/null
+++ b/addons/gamification/models/goal.py
@@ -0,0 +1,394 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from openerp import SUPERUSER_ID
+from openerp.osv import fields, osv
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
+from openerp.tools.safe_eval import safe_eval
+from openerp.tools.translate import _
+
+import logging
+import time
+from datetime import date, datetime, timedelta
+
+_logger = logging.getLogger(__name__)
+
+
+class gamification_goal_definition(osv.Model):
+ """Goal definition
+
+ A goal definition contains the way to evaluate an objective
+ Each module wanting to be able to set goals to the users needs to create
+ a new gamification_goal_definition
+ """
+ _name = 'gamification.goal.definition'
+ _description = 'Gamification goal definition'
+
+ def _get_suffix(self, cr, uid, ids, field_name, arg, context=None):
+ res = dict.fromkeys(ids, '')
+ for goal in self.browse(cr, uid, ids, context=context):
+ if goal.suffix and not goal.monetary:
+ res[goal.id] = goal.suffix
+ elif goal.monetary:
+ # use the current user's company currency
+ user = self.pool.get('res.users').browse(cr, uid, uid, context)
+ if goal.suffix:
+ res[goal.id] = "%s %s" % (user.company_id.currency_id.symbol, goal.suffix)
+ else:
+ res[goal.id] = user.company_id.currency_id.symbol
+ else:
+ res[goal.id] = ""
+ return res
+
+ _columns = {
+ 'name': fields.char('Goal Definition', required=True, translate=True),
+ 'description': fields.text('Goal Description'),
+ 'monetary': fields.boolean('Monetary Value', help="The target and current value are defined in the company currency."),
+ 'suffix': fields.char('Suffix', help="The unit of the target and current values", translate=True),
+ 'full_suffix': fields.function(_get_suffix, type="char", string="Full Suffix", help="The currency and suffix field"),
+ 'computation_mode': fields.selection([
+ ('manually', 'Recorded manually'),
+ ('count', 'Automatic: number of records'),
+ ('sum', 'Automatic: sum on a field'),
+ ('python', 'Automatic: execute a specific Python code'),
+ ],
+ string="Computation Mode",
+ help="Defined how will be computed the goals. The result of the operation will be stored in the field 'Current'.",
+ required=True),
+ 'display_mode': fields.selection([
+ ('progress', 'Progressive (using numerical values)'),
+ ('boolean', 'Exclusive (done or not-done)'),
+ ],
+ string="Displayed as", required=True),
+ 'model_id': fields.many2one('ir.model',
+ string='Model',
+ help='The model object for the field to evaluate'),
+ 'field_id': fields.many2one('ir.model.fields',
+ string='Field to Sum',
+ help='The field containing the value to evaluate'),
+ 'field_date_id': fields.many2one('ir.model.fields',
+ string='Date Field',
+ help='The date to use for the time period evaluated'),
+ 'domain': fields.char("Filter Domain",
+ help="Domain for filtering records. The rule can contain reference to 'user' that is a browse record of the current user, e.g. [('user_id', '=', user.id)].",
+ required=True),
+ 'compute_code': fields.text('Python Code',
+ help="Python code to be executed for each user. 'result' should contains the new current value. Evaluated user can be access through object.user_id."),
+ 'condition': fields.selection([
+ ('higher', 'The higher the better'),
+ ('lower', 'The lower the better')
+ ],
+ string='Goal Performance',
+ help='A goal is considered as completed when the current value is compared to the value to reach',
+ required=True),
+ 'action_id': fields.many2one('ir.actions.act_window', string="Action",
+ help="The action that will be called to update the goal value."),
+ 'res_id_field': fields.char("ID Field of user",
+ help="The field name on the user profile (res.users) containing the value for res_id for action.")
+ }
+
+ _defaults = {
+ 'condition': 'higher',
+ 'computation_mode': 'manually',
+ 'domain': "[]",
+ 'monetary': False,
+ 'display_mode': 'progress',
+ }
+
+ def number_following(self, cr, uid, model_name="mail.thread", context=None):
+ """Return the number of 'model_name' objects the user is following
+
+ The model specified in 'model_name' must inherit from mail.thread
+ """
+ user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+ return self.pool.get('mail.followers').search(cr, uid, [('res_model', '=', model_name), ('partner_id', '=', user.partner_id.id)], count=True, context=context)
+
+
+
+class gamification_goal(osv.Model):
+ """Goal instance for a user
+
+ An individual goal for a user on a specified time period"""
+
+ _name = 'gamification.goal'
+ _description = 'Gamification goal instance'
+ _inherit = 'mail.thread'
+
+ def _get_completion(self, cr, uid, ids, field_name, arg, context=None):
+ """Return the percentage of completeness of the goal, between 0 and 100"""
+ res = dict.fromkeys(ids, 0.0)
+ for goal in self.browse(cr, uid, ids, context=context):
+ if goal.definition_condition == 'higher':
+ if goal.current >= goal.target_goal:
+ res[goal.id] = 100.0
+ else:
+ res[goal.id] = round(100.0 * goal.current / goal.target_goal, 2)
+ elif goal.current < goal.target_goal:
+ # a goal 'lower than' has only two values possible: 0 or 100%
+ res[goal.id] = 100.0
+ else:
+ res[goal.id] = 0.0
+ return res
+
+ def on_change_definition_id(self, cr, uid, ids, definition_id=False, context=None):
+ goal_definition = self.pool.get('gamification.goal.definition')
+ if not definition_id:
+ return {'value': {'definition_id': False}}
+ goal_definition = goal_definition.browse(cr, uid, definition_id, context=context)
+ return {'value': {'computation_mode': goal_definition.computation_mode, 'definition_condition': goal_definition.condition}}
+
+ _columns = {
+ 'definition_id': fields.many2one('gamification.goal.definition', string='Goal Definition', required=True, ondelete="cascade"),
+ 'user_id': fields.many2one('res.users', string='User', required=True),
+ 'line_id': fields.many2one('gamification.challenge.line', string='Goal Line', ondelete="cascade"),
+ 'challenge_id': fields.related('line_id', 'challenge_id',
+ string="Challenge",
+ type='many2one',
+ relation='gamification.challenge',
+ store=True),
+ 'start_date': fields.date('Start Date'),
+ 'end_date': fields.date('End Date'), # no start and end = always active
+ 'target_goal': fields.float('To Reach',
+ required=True,
+ track_visibility='always'), # no goal = global index
+ 'current': fields.float('Current Value', required=True, track_visibility='always'),
+ 'completeness': fields.function(_get_completion, type='float', string='Completeness'),
+ 'state': fields.selection([
+ ('draft', 'Draft'),
+ ('inprogress', 'In progress'),
+ ('inprogress_update', 'In progress (to update)'),
+ ('reached', 'Reached'),
+ ('failed', 'Failed'),
+ ('canceled', 'Canceled'),
+ ],
+ string='State',
+ required=True,
+ track_visibility='always'),
+
+ 'computation_mode': fields.related('definition_id', 'computation_mode', type='char', string="Computation mode"),
+ 'remind_update_delay': fields.integer('Remind delay',
+ help="The number of days after which the user assigned to a manual goal will be reminded. Never reminded if no value is specified."),
+ 'last_update': fields.date('Last Update',
+ help="In case of manual goal, reminders are sent if the goal as not been updated for a while (defined in challenge). Ignored in case of non-manual goal or goal not linked to a challenge."),
+
+ 'definition_description': fields.related('definition_id', 'description', type='char', string='Definition Description', readonly=True),
+ 'definition_condition': fields.related('definition_id', 'condition', type='char', string='Definition Condition', readonly=True),
+ 'definition_suffix': fields.related('definition_id', 'full_suffix', type="char", string="Suffix", readonly=True),
+ 'definition_display': fields.related('definition_id', 'display_mode', type="char", string="Display Mode", readonly=True),
+ }
+
+ _defaults = {
+ 'current': 0,
+ 'state': 'draft',
+ 'start_date': fields.date.today,
+ }
+ _order = 'create_date desc, end_date desc, definition_id, id'
+
+ def _check_remind_delay(self, cr, uid, goal, context=None):
+ """Verify if a goal has not been updated for some time and send a
+ reminder message of needed.
+
+ :return: data to write on the goal object
+ """
+ if goal.remind_update_delay and goal.last_update:
+ delta_max = timedelta(days=goal.remind_update_delay)
+ last_update = datetime.strptime(goal.last_update, DF).date()
+ if date.today() - last_update > delta_max and goal.state == 'inprogress':
+ # generate a remind report
+ temp_obj = self.pool.get('email.template')
+ template_id = self.pool['ir.model.data'].get_object(cr, uid, 'gamification', 'email_template_goal_reminder', context)
+ body_html = temp_obj.render_template(cr, uid, template_id.body_html, 'gamification.goal', goal.id, context=context)
+
+ self.message_post(cr, uid, goal.id, body=body_html, partner_ids=[goal.user_id.partner_id.id], context=context, subtype='mail.mt_comment')
+ return {'state': 'inprogress_update'}
+ return {}
+
+ def update(self, cr, uid, ids, context=None):
+ """Update the goals to recomputes values and change of states
+
+ If a manual goal is not updated for enough time, the user will be
+ reminded to do so (done only once, in 'inprogress' state).
+ If a goal reaches the target value, the status is set to reached
+ If the end date is passed (at least +1 day, time not considered) without
+ the target value being reached, the goal is set as failed."""
+ if context is None:
+ context = {}
+
+ for goal in self.browse(cr, uid, ids, context=context):
+ towrite = {}
+ if goal.state in ('draft', 'canceled'):
+ # skip if goal draft or canceled
+ continue
+
+ if goal.definition_id.computation_mode == 'manually':
+ towrite.update(self._check_remind_delay(cr, uid, goal, context))
+
+ elif goal.definition_id.computation_mode == 'python':
+ # execute the chosen method
+ cxt = {
+ 'self': self.pool.get('gamification.goal'),
+ 'object': goal,
+ 'pool': self.pool,
+ 'cr': cr,
+ 'context': dict(context), # copy context to prevent side-effects of eval
+ 'uid': uid,
+ 'result': False,
+ 'date': date, 'datetime': datetime, 'timedelta': timedelta, 'time': time
+ }
+ code = goal.definition_id.compute_code.strip()
+ safe_eval(code, cxt, mode="exec", nocopy=True)
+ # the result of the evaluated codeis put in the 'result' local variable, propagated to the context
+ result = cxt.get('result', False)
+ if result and type(result) in (float, int, long):
+ if result != goal.current:
+ towrite['current'] = result
+ else:
+ _logger.exception(_('Invalid return content from the evaluation of %s' % code))
+
+ else: # count or sum
+ obj = self.pool.get(goal.definition_id.model_id.model)
+ field_date_name = goal.definition_id.field_date_id.name
+
+ # eval the domain with user replaced by goal user object
+ domain = safe_eval(goal.definition_id.domain, {'user': goal.user_id})
+
+ # add temporal clause(s) to the domain if fields are filled on the goal
+ if goal.start_date and field_date_name:
+ domain.append((field_date_name, '>=', goal.start_date))
+ if goal.end_date and field_date_name:
+ domain.append((field_date_name, '<=', goal.end_date))
+
+ if goal.definition_id.computation_mode == 'sum':
+ field_name = goal.definition_id.field_id.name
+ res = obj.read_group(cr, uid, domain, [field_name], [''], context=context)
+ new_value = res and res[0][field_name] or 0.0
+
+ else: # computation mode = count
+ new_value = obj.search(cr, uid, domain, context=context, count=True)
+
+ # avoid useless write if the new value is the same as the old one
+ if new_value != goal.current:
+ towrite['current'] = new_value
+
+ # check goal target reached
+ if (goal.definition_condition == 'higher' and towrite.get('current', goal.current) >= goal.target_goal) or (goal.definition_condition == 'lower' and towrite.get('current', goal.current) <= goal.target_goal):
+ towrite['state'] = 'reached'
+
+ # check goal failure
+ elif goal.end_date and fields.date.today() > goal.end_date:
+ towrite['state'] = 'failed'
+ if towrite:
+ self.write(cr, uid, [goal.id], towrite, context=context)
+ return True
+
+ def action_start(self, cr, uid, ids, context=None):
+ """Mark a goal as started.
+
+ This should only be used when creating goals manually (in draft state)"""
+ self.write(cr, uid, ids, {'state': 'inprogress'}, context=context)
+ return self.update(cr, uid, ids, context=context)
+
+ def action_reach(self, cr, uid, ids, context=None):
+ """Mark a goal as reached.
+
+ If the target goal condition is not met, the state will be reset to In
+ Progress at the next goal update until the end date."""
+ return self.write(cr, uid, ids, {'state': 'reached'}, context=context)
+
+ def action_fail(self, cr, uid, ids, context=None):
+ """Set the state of the goal to failed.
+
+ A failed goal will be ignored in future checks."""
+ return self.write(cr, uid, ids, {'state': 'failed'}, context=context)
+
+ def action_cancel(self, cr, uid, ids, context=None):
+ """Reset the completion after setting a goal as reached or failed.
+
+ This is only the current state, if the date and/or target criterias
+ match the conditions for a change of state, this will be applied at the
+ next goal update."""
+ return self.write(cr, uid, ids, {'state': 'inprogress'}, context=context)
+
+ def create(self, cr, uid, vals, context=None):
+ """Overwrite the create method to add a 'no_remind_goal' field to True"""
+ if context is None:
+ context = {}
+ context['no_remind_goal'] = True
+ return super(gamification_goal, self).create(cr, uid, vals, context=context)
+
+ def write(self, cr, uid, ids, vals, context=None):
+ """Overwrite the write method to update the last_update field to today
+
+ If the current value is changed and the report frequency is set to On
+ change, a report is generated
+ """
+ if context is None:
+ context = {}
+ vals['last_update'] = fields.date.today()
+ result = super(gamification_goal, self).write(cr, uid, ids, vals, context=context)
+ for goal in self.browse(cr, uid, ids, context=context):
+ if goal.state != "draft" and ('definition_id' in vals or 'user_id' in vals):
+ # avoid drag&drop in kanban view
+ raise osv.except_osv(_('Error!'), _('Can not modify the configuration of a started goal'))
+
+ if vals.get('current'):
+ if 'no_remind_goal' in context:
+ # new goals should not be reported
+ continue
+
+ if goal.challenge_id and goal.challenge_id.report_message_frequency == 'onchange':
+ self.pool.get('gamification.challenge').report_progress(cr, SUPERUSER_ID, goal.challenge_id, users=[goal.user_id], context=context)
+ return result
+
+ def get_action(self, cr, uid, goal_id, context=None):
+ """Get the ir.action related to update the goal
+
+ In case of a manual goal, should return a wizard to update the value
+ :return: action description in a dictionnary
+ """
+ goal = self.browse(cr, uid, goal_id, context=context)
+
+ if goal.definition_id.action_id:
+ # open a the action linked to the goal
+ action = goal.definition_id.action_id.read()[0]
+
+ if goal.definition_id.res_id_field:
+ current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+ action['res_id'] = safe_eval(goal.definition_id.res_id_field, {'user': current_user})
+
+ # if one element to display, should see it in form mode if possible
+ action['views'] = [(view_id, mode) for (view_id, mode) in action['views'] if mode == 'form'] or action['views']
+ return action
+
+ if goal.computation_mode == 'manually':
+ # open a wizard window to update the value manually
+ action = {
+ 'name': _("Update %s") % goal.definition_id.name,
+ 'id': goal_id,
+ 'type': 'ir.actions.act_window',
+ 'views': [[False, 'form']],
+ 'target': 'new',
+ 'context': {'default_goal_id': goal_id, 'default_current': goal.current},
+ 'res_model': 'gamification.goal.wizard'
+ }
+ return action
+
+ return False
diff --git a/addons/gamification/models/res_users.py b/addons/gamification/models/res_users.py
new file mode 100644
index 00000000000..408ddddf082
--- /dev/null
+++ b/addons/gamification/models/res_users.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from openerp.osv import osv
+from challenge import MAX_VISIBILITY_RANKING
+
+class res_users_gamification_group(osv.Model):
+ """ Update of res.users class
+ - if adding groups to an user, check gamification.challenge linked to
+ this group, and the user. This is done by overriding the write method.
+ """
+ _name = 'res.users'
+ _inherit = ['res.users']
+
+ def write(self, cr, uid, ids, vals, context=None):
+ """Overwrite to autosubscribe users if added to a group marked as autojoin, user will be added to challenge"""
+ write_res = super(res_users_gamification_group, self).write(cr, uid, ids, vals, context=context)
+ if vals.get('groups_id'):
+ # form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
+ user_group_ids = [command[1] for command in vals['groups_id'] if command[0] == 4]
+ user_group_ids += [id for command in vals['groups_id'] if command[0] == 6 for id in command[2]]
+
+ challenge_obj = self.pool.get('gamification.challenge')
+ challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', user_group_ids)], context=context)
+ if challenge_ids:
+ challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, user_id) for user_id in ids]}, context=context)
+ return write_res
+
+ def create(self, cr, uid, vals, context=None):
+ """Overwrite to autosubscribe users if added to a group marked as autojoin, user will be added to challenge"""
+ write_res = super(res_users_gamification_group, self).create(cr, uid, vals, context=context)
+ if vals.get('groups_id'):
+ # form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
+ user_group_ids = [command[1] for command in vals['groups_id'] if command[0] == 4]
+ user_group_ids += [id for command in vals['groups_id'] if command[0] == 6 for id in command[2]]
+
+ challenge_obj = self.pool.get('gamification.challenge')
+ challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', user_group_ids)], context=context)
+ if challenge_ids:
+ challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, write_res)]}, context=context)
+ return write_res
+
+ # def get_goals_todo_info(self, cr, uid, context=None):
+
+ def get_serialised_gamification_summary(self, cr, uid, context=None):
+ return self._serialised_goals_summary(cr, uid, user_id=uid, context=context)
+
+ def _serialised_goals_summary(self, cr, uid, user_id, context=None):
+ """Return a serialised list of goals assigned to the user, grouped by challenge
+
+ [
+ {
+ 'id': ,
+ 'name': ,
+ 'visibility_mode': ,
+ 'currency': ,
+ 'lines': [(see gamification_challenge._get_serialized_challenge_lines() format)]
+ },
+ ]
+ """
+ all_goals_info = []
+ challenge_obj = self.pool.get('gamification.challenge')
+
+ user = self.browse(cr, uid, uid, context=context)
+ challenge_ids = challenge_obj.search(cr, uid, [('user_ids', 'in', uid), ('state', '=', 'inprogress')], context=context)
+ for challenge in challenge_obj.browse(cr, uid, challenge_ids, context=context):
+ # serialize goals info to be able to use it in javascript
+ all_goals_info.append({
+ 'id': challenge.id,
+ 'name': challenge.name,
+ 'visibility_mode': challenge.visibility_mode,
+ 'currency': user.company_id.currency_id.id,
+ 'lines': challenge_obj._get_serialized_challenge_lines(cr, uid, challenge, user_id, restrict_top=MAX_VISIBILITY_RANKING, context=context),
+ })
+
+ return all_goals_info
+
+ def get_challenge_suggestions(self, cr, uid, context=None):
+ """Return the list of challenges suggested to the user"""
+ challenge_info = []
+ challenge_obj = self.pool.get('gamification.challenge')
+ challenge_ids = challenge_obj.search(cr, uid, [('invited_user_ids', 'in', uid), ('state', '=', 'inprogress')], context=context)
+ for challenge in challenge_obj.browse(cr, uid, challenge_ids, context=context):
+ values = {
+ 'id': challenge.id,
+ 'name': challenge.name,
+ 'description': challenge.description,
+ }
+ challenge_info.append(values)
+ return challenge_info
+
+
+class res_groups_gamification_group(osv.Model):
+ """ Update of res.groups class
+ - if adding users from a group, check gamification.challenge linked to
+ this group, and the user. This is done by overriding the write method.
+ """
+ _name = 'res.groups'
+ _inherit = 'res.groups'
+
+ # No need to overwrite create as very unlikely to be the value in the autojoin_group_id field
+ def write(self, cr, uid, ids, vals, context=None):
+ """Overwrite to autosubscribe users if add users to a group marked as autojoin, these will be added to the challenge"""
+ write_res = super(res_groups_gamification_group, self).write(cr, uid, ids, vals, context=context)
+ if vals.get('users'):
+ # form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
+ user_ids = [command[1] for command in vals['users'] if command[0] == 4]
+ user_ids += [id for command in vals['users'] if command[0] == 6 for id in command[2]]
+
+ challenge_obj = self.pool.get('gamification.challenge')
+ challenge_ids = challenge_obj.search(cr, uid, [('autojoin_group_id', 'in', ids)], context=context)
+ if challenge_ids:
+ challenge_obj.write(cr, uid, challenge_ids, {'user_ids': [(4, user_id) for user_id in user_ids]}, context=context)
+ return write_res
diff --git a/addons/gamification/security/gamification_security.xml b/addons/gamification/security/gamification_security.xml
new file mode 100644
index 00000000000..904ba50721e
--- /dev/null
+++ b/addons/gamification/security/gamification_security.xml
@@ -0,0 +1,43 @@
+
+
+
+
+ Gamification
+
+ 17
+
+
+ Manager
+
+
+
+
+
+ User can only see his/her goals or goal from the same challenge in board visibility
+
+
+
+
+
+
+ [
+ '|',
+ ('user_id','=',user.id),
+ '&',
+ ('challenge_id.user_ids','in',user.id),
+ ('challenge_id.visibility_mode','=','ranking')]
+
+
+
+ Gamification Manager can see any goal
+
+
+
+
+
+
+ [(1, '=', 1)]
+
+
+
+
diff --git a/addons/gamification/security/ir.model.access.csv b/addons/gamification/security/ir.model.access.csv
new file mode 100644
index 00000000000..932c5d97c62
--- /dev/null
+++ b/addons/gamification/security/ir.model.access.csv
@@ -0,0 +1,19 @@
+id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
+
+goal_employee,"Goal Employee",model_gamification_goal,base.group_user,1,1,0,0
+goal_manager,"Goal Manager",model_gamification_goal,group_goal_manager,1,1,1,1
+
+goal_definition_employee,"Goal Definition Employee",model_gamification_goal_definition,base.group_user,1,0,0,0
+goal_definition_manager,"Goal Definition Manager",model_gamification_goal_definition,group_goal_manager,1,1,1,1
+
+challenge_employee,"Goal Challenge Employee",model_gamification_challenge,base.group_user,1,0,0,0
+challenge_manager,"Goal Challenge Manager",model_gamification_challenge,group_goal_manager,1,1,1,1
+
+challenge_line_employee,"Challenge Line Employee",model_gamification_challenge_line,base.group_user,1,0,0,0
+challenge_line_manager,"Challenge Line Manager",model_gamification_challenge_line,group_goal_manager,1,1,1,1
+
+badge_employee,"Badge Employee",model_gamification_badge,base.group_user,1,0,0,0
+badge_manager,"Badge Manager",model_gamification_badge,group_goal_manager,1,1,1,1
+
+badge_user_employee,"Badge-user Employee",model_gamification_badge_user,base.group_user,1,1,1,0
+badge_user_manager,"Badge-user Manager",model_gamification_badge_user,group_goal_manager,1,1,1,1
diff --git a/addons/gamification/static/description/icon.png b/addons/gamification/static/description/icon.png
new file mode 100644
index 00000000000..52a75f726ba
Binary files /dev/null and b/addons/gamification/static/description/icon.png differ
diff --git a/addons/gamification/static/src/css/Makefile b/addons/gamification/static/src/css/Makefile
new file mode 100644
index 00000000000..3a6a70cf59b
--- /dev/null
+++ b/addons/gamification/static/src/css/Makefile
@@ -0,0 +1,3 @@
+gamification.css: gamification.sass
+ sass --trace -t expanded gamification.sass gamification.css
+
diff --git a/addons/gamification/static/src/css/gamification.css b/addons/gamification/static/src/css/gamification.css
new file mode 100644
index 00000000000..4ac3a0340fa
--- /dev/null
+++ b/addons/gamification/static/src/css/gamification.css
@@ -0,0 +1,190 @@
+@charset "UTF-8";
+.openerp .oe_kanban_view .oe_kanban_card.oe_kanban_goal {
+ width: 230px;
+ min-height: 200px;
+}
+.openerp .oe_kanban_view .oe_kanban_card.oe_kanban_badge {
+ width: 250px;
+ min-height: 150px;
+}
+.openerp .oe_kanban_badge_avatars {
+ margin-top: 8px;
+}
+.openerp .oe_kanban_badge_avatars img {
+ width: 30px;
+ height: 30px;
+ padding-left: 0;
+ margin-top: 3px;
+ border-radius: 2px;
+ -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
+}
+.openerp .oe_kanban_goal .oe_goal_state_block {
+ width: 200px;
+ height: 130px;
+ margin: auto;
+ margin-bottom: -20px;
+}
+.openerp .oe_kanban_goal .oe_goal_state_block .oe_goal_state {
+ font-size: 2.5em;
+ font-weight: bold;
+ padding-top: 30px;
+}
+.openerp .oe_kanban_goal .oe_goal_state_block .oe_goal_state.oe_e {
+ font-size: 7em;
+}
+.openerp .oe_kanban_goal .oe_goal_state_block, .openerp .oe_kanban_goal p {
+ text-align: center;
+}
+.openerp .oe_kanban_content .oe_goal_gauge:first-child {
+ margin: auto;
+}
+.openerp .oe_kanban_content .oe_goal_gauge svg {
+ margin-top: -20px;
+}
+.openerp .oe_no_overflow {
+ overflow: hidden;
+}
+.openerp .oe_red {
+ color: red;
+}
+.openerp .oe_green {
+ color: green;
+}
+.openerp .oe_orange {
+ color: orange;
+}
+.openerp .oe_form td .oe_no_padding {
+ margin-left: -6px;
+}
+.openerp .oe_mail_wall .oe_mail_wall_aside {
+ margin-top: 15px;
+ position: relative;
+ display: inline-block;
+ vertical-align: top;
+ width: 280px;
+ border-radius: 2px;
+}
+.openerp .oe_mail_wall .oe_mail_wall_aside .oe_gamification_challenge_list {
+ background-color: #ededf6;
+}
+.openerp .oe_mail_wall .oe_mail_wall_aside .oe_gamification_suggestion {
+ background-color: #d3def1;
+}
+.openerp .oe_mail_wall .oe_mail_wall_aside .oe_gamification_suggestion ul {
+ padding-left: 15px;
+}
+.openerp .oe_mail_wall .oe_mail_wall_aside h4, .openerp .oe_mail_wall .oe_mail_wall_aside .oe_goals_list .oe_thead {
+ text-align: center;
+ padding-bottom: 15px;
+}
+.openerp .oe_mail_wall .oe_mail_wall_aside > div {
+ border-bottom: solid 5px white;
+ border-radius: 2px;
+}
+.openerp .oe_mail_wall .oe_goal {
+ border-bottom: solid 3px white;
+ padding: 5px 10px;
+}
+.openerp .oe_mail_wall .oe_goal .oe_update_challenge.oe_e, .openerp .oe_mail_wall .oe_goal .oe_goal_action.oe_e {
+ visibility: hidden;
+ font-size: 25px;
+ float: right;
+ position: relative;
+}
+.openerp .oe_mail_wall .oe_goal div:hover > .oe_update_challenge, .openerp .oe_mail_wall .oe_goal div:hover > .oe_goal_action, .openerp .oe_mail_wall .oe_goal th:hover > .oe_goal_action {
+ visibility: visible;
+}
+.openerp .oe_mail_wall .oe_goal .oe_goals_list {
+ padding-left: 0;
+ margin-top: 5px;
+ margin-bottom: 10px;
+ width: 100%;
+}
+.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_no_progress div {
+ display: inline-block;
+}
+.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_no_progress::before {
+ content: "•";
+ padding-right: 4px;
+}
+.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_no_progress.oe_goal_reached::before {
+ content: "✓";
+ padding-right: 0;
+ margin-left: -2px;
+}
+.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_no_progress.oe_goal_reached .oe_cell {
+ text-decoration: line-through;
+}
+.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_cell.oe_goal_current {
+ font-size: 150%;
+ font-weight: bold;
+ min-width: 50px;
+ padding: 0 5px;
+}
+.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_goal_outer_box {
+ display: inline-block;
+ position: relative;
+ z-index: 0;
+ vertical-align: middle;
+ width: 100%;
+ border: solid 1px rgba(0, 0, 0, 0.03);
+ border-radius: 2px;
+}
+.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_goal_outer_box.oe_no_progress {
+ border: none;
+}
+.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_goal_progress_background {
+ background-color: white;
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ z-index: -2;
+ top: 0;
+ left: 0;
+}
+.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_goal_progress {
+ background-color: #d4e9de;
+ position: absolute;
+ height: 100%;
+ width: 0;
+ z-index: -1;
+ top: 0;
+ left: 0;
+}
+.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_thead {
+ font-weight: normal;
+ padding: 5px;
+ text-align: center;
+}
+.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_cell {
+ padding: 3px 0;
+}
+.openerp .oe_mail_wall .oe_table.oe_goals_list .col0 {
+ font-size: 200%;
+ font-weight: bold;
+ width: 25px;
+ text-align: center;
+}
+.openerp .oe_mail_wall .oe_table.oe_goals_list .col1 {
+ padding: 0 5px;
+}
+.openerp .oe_mail_wall .oe_table.oe_goals_list .col2 {
+ width: auto;
+}
+.openerp .oe_mail_wall .oe_user_avatar {
+ width: 24px;
+ padding-right: 5px;
+}
+.openerp .oe_mail_wall .oe_mail {
+ display: inline-block;
+}
+.openerp .oe_mail_wall .oe_table {
+ display: table;
+}
+.openerp .oe_mail_wall .oe_row {
+ display: table-row;
+}
+.openerp .oe_mail_wall .oe_cell {
+ display: table-cell;
+ vertical-align: middle;
+}
diff --git a/addons/gamification/static/src/css/gamification.sass b/addons/gamification/static/src/css/gamification.sass
new file mode 100644
index 00000000000..3bb6b570e66
--- /dev/null
+++ b/addons/gamification/static/src/css/gamification.sass
@@ -0,0 +1,181 @@
+@charset "utf-8"
+
+.openerp
+ // Kanban views
+ .oe_kanban_view
+ .oe_kanban_card.oe_kanban_goal
+ width: 230px
+ min-height: 200px
+ .oe_kanban_card.oe_kanban_badge
+ width: 250px
+ min-height: 150px
+ .oe_kanban_badge_avatars
+ margin-top: 8px
+ img
+ width: 30px
+ height: 30px
+ padding-left: 0
+ margin-top: 3px
+ border-radius: 2px
+ -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2)
+
+ .oe_kanban_goal
+ .oe_goal_state_block
+ width: 200px
+ height: 130px
+ margin: auto
+ margin-bottom: -20px
+ .oe_goal_state
+ font-size: 2.5em
+ font-weight: bold
+ padding-top: 30px
+ .oe_goal_state.oe_e
+ font-size: 7em
+ .oe_goal_state_block,p
+ text-align: center
+
+ .oe_kanban_content
+ .oe_goal_gauge:first-child
+ margin: auto /* avoid margin-right: 16px */
+ .oe_goal_gauge
+ svg
+ margin-top: -20px
+ .oe_no_overflow
+ overflow: hidden
+
+
+ .oe_red
+ color: red
+ .oe_green
+ color: green
+ .oe_orange
+ color: orange
+
+
+ // compensate padding from .openerp .oe_form td.oe_form_group_cell + .oe_form_group_cell
+ .oe_form td .oe_no_padding
+ margin-left: -6px
+
+
+ .oe_mail_wall
+ .oe_mail_wall_aside
+ margin-top: 15px
+ position: relative
+ display: inline-block
+ vertical-align: top
+ width: 280px
+ border-radius: 2px
+
+ .oe_gamification_challenge_list
+ background-color: #EDEDF6
+ .oe_gamification_suggestion
+ background-color: rgb(211, 222, 241)
+ ul
+ padding-left: 15px
+
+ h4, .oe_goals_list .oe_thead
+ text-align: center
+ padding-bottom: 15px
+
+ .oe_mail_wall_aside > div
+ border-bottom: solid 5px white
+ border-radius: 2px
+
+ .oe_goal
+ border-bottom: solid 3px white
+ padding: 5px 10px
+ .oe_update_challenge.oe_e, .oe_goal_action.oe_e
+ visibility: hidden
+ font-size: 25px
+ float: right
+ position: relative
+ div:hover > .oe_update_challenge, div:hover > .oe_goal_action, th:hover > .oe_goal_action
+ visibility: visible
+
+ .oe_goals_list
+ padding-left: 0
+ margin-top: 5px
+ margin-bottom: 10px
+ width: 100%
+
+ .oe_no_progress
+ div
+ display: inline-block
+ .oe_no_progress::before
+ content: "•"
+ padding-right: 4px
+ .oe_no_progress.oe_goal_reached::before
+ content: "✓"
+ padding-right: 0
+ margin-left: -2px
+ .oe_no_progress.oe_goal_reached
+ .oe_cell
+ text-decoration: line-through
+
+ .oe_cell.oe_goal_current
+ font-size: 150%
+ font-weight: bold
+ min-width: 50px
+ padding: 0 5px
+
+ .oe_goal_outer_box
+ display: inline-block
+ position: relative
+ z-index: 0
+ vertical-align: middle
+ width: 100%
+ border: solid 1px rgba(0,0,0,0.03)
+ border-radius: 2px
+ .oe_goal_outer_box.oe_no_progress
+ border: none
+
+ .oe_goal_progress_background
+ background-color: white
+ position: absolute
+ height: 100%
+ width: 100%
+ z-index: -2
+ top: 0
+ left: 0
+
+ .oe_goal_progress
+ background-color: rgb(212, 233, 222)
+ position: absolute
+ height: 100%
+ width: 0
+ z-index: -1
+ top: 0
+ left: 0
+
+ .oe_thead
+ font-weight: normal
+ padding: 5px
+ text-align: center
+ .oe_cell
+ padding: 3px 0
+
+ .oe_table.oe_goals_list
+ .col0
+ font-size: 200%
+ font-weight: bold
+ width: 25px
+ text-align: center
+ .col1
+ padding: 0 5px
+ .col2
+ width: auto
+
+ .oe_user_avatar
+ width: 24px
+ padding-right: 5px
+
+ .oe_mail
+ display: inline-block
+
+ .oe_table
+ display: table
+ .oe_row
+ display: table-row
+ .oe_cell
+ display: table-cell
+ vertical-align: middle
diff --git a/addons/gamification/static/src/js/gamification.js b/addons/gamification/static/src/js/gamification.js
new file mode 100644
index 00000000000..2a8db5f369d
--- /dev/null
+++ b/addons/gamification/static/src/js/gamification.js
@@ -0,0 +1,148 @@
+openerp.gamification = function(instance) {
+ var QWeb = instance.web.qweb;
+
+ instance.gamification.Sidebar = instance.web.Widget.extend({
+ template: 'gamification.UserWallSidebar',
+ init: function (parent, action) {
+ var self = this;
+ this._super(parent, action);
+ this.deferred = $.Deferred();
+ this.goals_info = {};
+ this.challenge_suggestions = {};
+ $(document).off('keydown.klistener');
+ },
+ events: {
+ // update a challenge and related goals
+ 'click a.oe_update_challenge': function(event) {
+ var self = this;
+ var challenge_id = parseInt(event.currentTarget.id, 10);
+ var goals_updated = new instance.web.Model('gamification.challenge').call('quick_update', [challenge_id]);
+ $.when(goals_updated).done(function() {
+ self.get_goal_todo_info();
+ });
+ },
+ // action to modify a goal
+ 'click a.oe_goal_action': function(event) {
+ var self = this;
+ var goal_id = parseInt(event.currentTarget.id, 10);
+ var goal_action = new instance.web.Model('gamification.goal').call('get_action', [goal_id]).then(function(res) {
+ goal_action['action'] = res;
+ });
+ $.when(goal_action).done(function() {
+ var action = self.do_action(goal_action.action);
+ $.when(action).done(function () {
+ new instance.web.Model('gamification.goal').call('update', [[goal_id]]).then(function(res) {
+ self.get_goal_todo_info();
+ });
+ });
+ });
+ },
+ // get more info about a challenge request
+ 'click a.oe_challenge_reply': function(event) {
+ var self = this;
+ var challenge_id = parseInt(event.currentTarget.id, 10);
+ var challenge_action = new instance.web.Model('gamification.challenge').call('reply_challenge_wizard', [challenge_id]).then(function(res) {
+ challenge_action['action'] = res;
+ });
+ $.when(challenge_action).done(function() {
+ self.do_action(challenge_action.action).done(function () {
+ self.get_goal_todo_info();
+ });
+ });
+ },
+ 'click .oe_goal h4': function(event) {
+ var self = this;
+ this.kkeys = [];
+ $(document).on('keydown.klistener', function(event) {
+ if ("37,38,39,40,65,66".indexOf(event.keyCode) < 0) {
+ $(document).off('keydown.klistener');
+ } else {
+ self.kkeys.push(event.keyCode);
+ if (self.kkeys.toString().indexOf("38,38,40,40,37,39,37,39,66,65") >= 0) {
+ new instance.web.Model('gamification.badge').call('check_progress', []);
+ $(document).off('keydown.klistener');
+ }
+ }
+ });
+ }
+ },
+ start: function() {
+ var self = this;
+ this._super.apply(this, arguments);
+ self.get_goal_todo_info();
+ self.get_challenge_suggestions();
+ },
+ get_goal_todo_info: function() {
+ var self = this;
+ var challenges = new instance.web.Model('res.users').call('get_serialised_gamification_summary', []).then(function(result) {
+ if (result.length === 0) {
+ self.$el.find(".oe_gamification_challenge_list").hide();
+ } else {
+ self.$el.find(".oe_gamification_challenge_list").empty();
+ _.each(result, function(item){
+ var $item = $(QWeb.render("gamification.ChallengeSummary", {challenge: item}));
+ self.render_money_fields($item);
+ self.render_user_avatars($item);
+ self.$el.find('.oe_gamification_challenge_list').append($item);
+ });
+ }
+ });
+ },
+ get_challenge_suggestions: function() {
+ var self = this;
+ var challenge_suggestions = new instance.web.Model('res.users').call('get_challenge_suggestions', []).then(function(result) {
+ if (result.length === 0) {
+ self.$el.find(".oe_gamification_suggestion").hide();
+ } else {
+ var $item = $(QWeb.render("gamification.ChallengeSuggestion", {challenges: result}));
+ self.$el.find('.oe_gamification_suggestion').append($item);
+ }
+ });
+ },
+ render_money_fields: function(item) {
+ var self = this;
+ self.dfm = new instance.web.form.DefaultFieldManager(self);
+ // Generate a FieldMonetary for each .oe_goal_field_monetary
+ item.find(".oe_goal_field_monetary").each(function() {
+ var currency_id = parseInt( $(this).attr('data-id'), 10);
+ money_field = new instance.web.form.FieldMonetary(self.dfm, {
+ attrs: {
+ modifiers: '{"readonly": true}'
+ }
+ });
+ money_field.set('currency', currency_id);
+ money_field.get_currency_info();
+ money_field.set('value', parseInt($(this).text(), 10));
+ money_field.replace($(this));
+ });
+ },
+ render_user_avatars: function(item) {
+ var self = this;
+ item.find(".oe_user_avatar").each(function() {
+ var user_id = parseInt( $(this).attr('data-id'), 10);
+ var url = instance.session.url('/web/binary/image', {model: 'res.users', field: 'image_small', id: user_id});
+ $(this).attr("src", url);
+ });
+ }
+ });
+
+ instance.mail.Widget.include({
+ start: function() {
+ this._super();
+ var sidebar = new instance.gamification.Sidebar(this);
+ sidebar.appendTo($('.oe_mail_wall_aside'));
+ },
+ });
+
+ instance.web_kanban.KanbanRecord.include({
+ // open related goals when clicking on challenge kanban view
+ on_card_clicked: function() {
+ if (this.view.dataset.model === 'gamification.challenge') {
+ this.$('.oe_kanban_project_list a').first().click();
+ } else {
+ this._super.apply(this, arguments);
+ }
+ },
+ });
+
+};
diff --git a/addons/gamification/static/src/xml/gamification.xml b/addons/gamification/static/src/xml/gamification.xml
new file mode 100644
index 00000000000..6316ca886f0
--- /dev/null
+++ b/addons/gamification/static/src/xml/gamification.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
diff --git a/addons/gamification/tests/__init__.py b/addons/gamification/tests/__init__.py
new file mode 100644
index 00000000000..9cafd242b28
--- /dev/null
+++ b/addons/gamification/tests/__init__.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2013 OpenERP S.A.
+#
+# 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 .
+#
+##############################################################################
+
+from . import test_challenge
+
+checks = [
+ test_challenge,
+]
diff --git a/addons/gamification/tests/test_challenge.py b/addons/gamification/tests/test_challenge.py
new file mode 100644
index 00000000000..6b93da727ac
--- /dev/null
+++ b/addons/gamification/tests/test_challenge.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2013 OpenERP S.A.
+#
+# 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 .
+#
+##############################################################################
+
+from openerp.tests import common
+
+
+class test_challenge(common.TransactionCase):
+
+ def setUp(self):
+ super(test_challenge, self).setUp()
+ cr, uid = self.cr, self.uid
+ self.data_obj = self.registry('ir.model.data')
+ self.user_obj = self.registry('res.users')
+
+ self.challenge_obj = self.registry('gamification.challenge')
+ self.line_obj = self.registry('gamification.challenge.line')
+ self.goal_obj = self.registry('gamification.goal')
+ self.badge_obj = self.registry('gamification.badge')
+ self.badge_user_obj = self.registry('gamification.badge.user')
+
+ self.demo_user_id = self.data_obj.get_object_reference(cr, uid, 'base', 'user_demo')[1]
+ self.group_user_id = self.data_obj.get_object_reference(cr, uid, 'base', 'group_user')[1]
+ self.challenge_base_id = self.data_obj.get_object_reference(cr, uid, 'gamification', 'challenge_base_discover')[1]
+ self.definition_timezone_id = self.data_obj.get_object_reference(cr, uid, 'gamification', 'definition_base_timezone')[1]
+ self.badge_id = self.data_obj.get_object_reference(cr, uid, 'gamification', 'badge_good_job')[1]
+
+ def test_00_join_challenge(self):
+ cr, uid, context = self.cr, self.uid, {}
+
+ user_ids = self.user_obj.search(cr, uid, [('groups_id', '=', self.group_user_id)])
+ challenge = self.challenge_obj.browse(cr, uid, self.challenge_base_id, context=context)
+
+ self.assertGreaterEqual(len(challenge.user_ids), len(user_ids), "Not enough users in base challenge")
+
+ self.user_obj.create(cr, uid, {
+ 'name': 'R2D2',
+ 'login': 'r2d2@openerp.com',
+ 'email': 'r2d2@openerp.com',
+ 'groups_id': [(6, 0, [self.group_user_id])]
+ }, {'no_reset_password': True})
+
+ challenge = self.challenge_obj.browse(cr, uid, self.challenge_base_id, context=context)
+ self.assertGreaterEqual(len(challenge.user_ids), len(user_ids)+1, "These are not droids you are looking for")
+
+ def test_10_reach_challenge(self):
+ cr, uid, context = self.cr, self.uid, {}
+
+ self.challenge_obj.write(cr, uid, [self.challenge_base_id], {'state': 'inprogress'}, context=context)
+ challenge = self.challenge_obj.browse(cr, uid, self.challenge_base_id, context=context)
+ challenge_user_ids = [user.id for user in challenge.user_ids]
+
+ self.assertEqual(challenge.state, 'inprogress', "Challenge failed the change of state")
+
+ line_ids = self.line_obj.search(cr, uid, [('challenge_id', '=', self.challenge_base_id)], context=context)
+ goal_ids = self.goal_obj.search(cr, uid, [('challenge_id', '=', self.challenge_base_id), ('state', '!=', 'draft')], context=context)
+ self.assertEqual(len(goal_ids), len(line_ids)*len(challenge_user_ids), "Incorrect number of goals generated, should be 1 goal per user, per challenge line")
+
+ # demo user will set a timezone
+ self.user_obj.write(cr, uid, self.demo_user_id, {'tz': "Europe/Brussels"}, context=context)
+ goal_ids = self.goal_obj.search(cr, uid, [('user_id', '=', self.demo_user_id), ('definition_id', '=', self.definition_timezone_id)], context=context)
+
+ self.goal_obj.update(cr, uid, goal_ids, context=context)
+ reached_goal_ids = self.goal_obj.search(cr, uid, [('id', 'in', goal_ids), ('state', '=', 'reached')], context=context)
+ self.assertEqual(set(goal_ids), set(reached_goal_ids), "Not every goal was reached after changing timezone")
+
+ # reward for two firsts as admin may have timezone
+ self.challenge_obj.write(cr, uid, self.challenge_base_id, {'reward_first_id': self.badge_id, 'reward_second_id': self.badge_id}, context=context)
+ self.challenge_obj.write(cr, uid, self.challenge_base_id, {'state': 'done'}, context=context)
+
+ badge_ids = self.badge_user_obj.search(cr, uid, [('badge_id', '=', self.badge_id), ('user_id', '=', self.demo_user_id)])
+ self.assertGreater(len(badge_ids), 0, "Demo user has not received the badge")
\ No newline at end of file
diff --git a/addons/gamification/views/badge.xml b/addons/gamification/views/badge.xml
new file mode 100644
index 00000000000..0b37e62051d
--- /dev/null
+++ b/addons/gamification/views/badge.xml
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+ Badges
+ gamification.badge
+ kanban,tree,form
+
+
+ Click to create a badge.
+
+
+ A badge is a symbolic token granted to a user as a sign of reward.
+ It can be deserved automatically when some conditions are met or manually by users.
+ Some badges are harder than others to get with specific conditions.
+
+ Assign a list of goals to chosen users to evaluate them.
+ The challenge can use a period (weekly, monthly...) for automatic creation of goals.
+ The goals are created for the specified users or member of the group.
+
+ A goal definition is a technical model of goal defining a condition to reach.
+ The dates, values to reach or users are defined in goal instance.
+
+
+
+
+
+ Goal Definitions List
+ gamification.goal.definition
+
+
+
+
+
+
+
+
+
+
+ Goal Definitions Form
+ gamification.goal.definition
+
+
+
+
+
+
+ Goal Definition Search
+ gamification.goal.definition
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/gamification/wizard/__init__.py b/addons/gamification/wizard/__init__.py
new file mode 100644
index 00000000000..638fbef4373
--- /dev/null
+++ b/addons/gamification/wizard/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 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 update_goal
+import grant_badge
diff --git a/addons/gamification/wizard/grant_badge.py b/addons/gamification/wizard/grant_badge.py
new file mode 100644
index 00000000000..44f739fff4e
--- /dev/null
+++ b/addons/gamification/wizard/grant_badge.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from openerp.osv import fields, osv
+from openerp.tools.translate import _
+
+
+class grant_badge_wizard(osv.TransientModel):
+ """ Wizard allowing to grant a badge to a user"""
+
+ _name = 'gamification.badge.user.wizard'
+ _columns = {
+ 'user_id': fields.many2one("res.users", string='User', required=True),
+ 'badge_id': fields.many2one("gamification.badge", string='Badge', required=True),
+ 'comment': fields.text('Comment'),
+ }
+
+ def action_grant_badge(self, cr, uid, ids, context=None):
+ """Wizard action for sending a badge to a chosen user"""
+
+ badge_obj = self.pool.get('gamification.badge')
+ badge_user_obj = self.pool.get('gamification.badge.user')
+
+ for wiz in self.browse(cr, uid, ids, context=context):
+ if uid == wiz.user_id.id:
+ raise osv.except_osv(_('Warning!'), _('You can not grant a badge to yourself'))
+
+ #create the badge
+ values = {
+ 'user_id': wiz.user_id.id,
+ 'sender_id': uid,
+ 'badge_id': wiz.badge_id.id,
+ 'comment': wiz.comment,
+ }
+ badge_user = badge_user_obj.create(cr, uid, values, context=context)
+ result = badge_obj._send_badge(cr, uid, badge_user, context=context)
+
+ return result
diff --git a/addons/gamification/wizard/grant_badge.xml b/addons/gamification/wizard/grant_badge.xml
new file mode 100644
index 00000000000..e03dbac4096
--- /dev/null
+++ b/addons/gamification/wizard/grant_badge.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Grant Badge User Form
+ gamification.badge.user.wizard
+
+
+
+
+
+
+
+
+
diff --git a/addons/gamification/wizard/update_goal.py b/addons/gamification/wizard/update_goal.py
new file mode 100644
index 00000000000..cbda3fefbce
--- /dev/null
+++ b/addons/gamification/wizard/update_goal.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from openerp.osv import fields, osv
+
+class goal_manual_wizard(osv.TransientModel):
+ """Wizard to update a manual goal"""
+ _name = 'gamification.goal.wizard'
+ _columns = {
+ 'goal_id': fields.many2one("gamification.goal", string='Goal', required=True),
+ 'current': fields.float('Current'),
+ }
+
+ def action_update_current(self, cr, uid, ids, context=None):
+ """Wizard action for updating the current value"""
+
+ goal_obj = self.pool.get('gamification.goal')
+
+ for wiz in self.browse(cr, uid, ids, context=context):
+ towrite = {
+ 'current': wiz.current,
+ 'goal_id': wiz.goal_id.id,
+ }
+ goal_obj.write(cr, uid, [wiz.goal_id.id], towrite, context=context)
+ goal_obj.update(cr, uid, [wiz.goal_id.id], context=context)
+ return {}
diff --git a/addons/gamification/wizard/update_goal.xml b/addons/gamification/wizard/update_goal.xml
new file mode 100644
index 00000000000..793efab90ff
--- /dev/null
+++ b/addons/gamification/wizard/update_goal.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+ Update the current value of the Goal
+ gamification.goal.wizard
+
+
+
+
+
+
+
diff --git a/addons/gamification_sale_crm/__init__.py b/addons/gamification_sale_crm/__init__.py
new file mode 100644
index 00000000000..e14c014a1ef
--- /dev/null
+++ b/addons/gamification_sale_crm/__init__.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 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 .
+#
+##############################################################################
diff --git a/addons/gamification_sale_crm/__openerp__.py b/addons/gamification_sale_crm/__openerp__.py
new file mode 100644
index 00000000000..b2ec9a10428
--- /dev/null
+++ b/addons/gamification_sale_crm/__openerp__.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 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 .
+#
+##############################################################################
+{
+ 'name': 'CRM Gamification',
+ 'version': '1.0',
+ 'author': 'OpenERP SA',
+ 'category': 'hidden',
+ 'depends': ['gamification','sale_crm'],
+ 'description': """Example of goal definitions and challenges that can be used related to the usage of the CRM Sale module.""",
+
+ 'data': ['sale_crm_goals.xml'],
+ 'demo': ['sale_crm_goals_demo.xml'],
+ 'auto_install': True,
+}
diff --git a/addons/gamification_sale_crm/sale_crm_goals.xml b/addons/gamification_sale_crm/sale_crm_goals.xml
new file mode 100644
index 00000000000..b48cc8a3117
--- /dev/null
+++ b/addons/gamification_sale_crm/sale_crm_goals.xml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+ Total Invoiced
+
+ sum
+ True
+
+
+
+ [('state','!=','cancel'),('user_id','=',user.id),('type','=','out_invoice')]
+
+
+
+ New Leads
+ Based on the creation date
+ count
+ leads
+
+
+
+ [('user_id','=',user.id), '|', ('type', '=', 'lead'), ('type', '=', 'opportunity')]
+
+
+
+ Time to Qualify a Lead
+ The average number of days to open the case (lower than)
+ sum
+ lower
+ days
+
+
+
+ [('user_id','=',user.id),('type', '=', 'lead')]
+
+
+
+ Days to Close a Deal
+ The average number of days to close the case (lower than)
+ sum
+ lower
+ days
+
+
+
+ [('user_id','=',user.id)]
+
+
+
+
+ Logged Calls
+ Log a certain number of calls to reach this goal
+ count
+ calls
+
+
+ [('user_id','=',user.id),('state','=','done')]
+
+
+
+ New Opportunities
+ Based on the opening date
+ count
+ opportunities
+
+
+ [('user_id','=',user.id),('type','=','opportunity')]
+
+
+
+ New Sales Orders
+ Based on the creation date
+ count
+ orders
+
+
+ [('user_id','=',user.id),('state','not in',('draft', 'sent', 'cancel'))]
+
+
+
+ Paid Sales Orders
+ Based on the invoice date
+ count
+ orders
+
+
+ [('state','=','paid'),('user_id','=',user.id),('type','=','out_invoice')]
+
+
+ Total Paid Sales Orders
+ Based on the invoice date
+ count
+ True
+
+
+
+ [('state','=','paid'),('user_id','=',user.id),('type','=','out_invoice')]
+
+
+
+
+ Customer Refunds
+ Refund the least customers (lower than)
+ count
+ lower
+ invoices
+
+
+ [('state','!=','cancel'),('user_id','=',user.id),('type','=','out_refund')]
+
+
+ Total Customer Refunds
+ The total refunded value is a negative value. Validated when higher (min refunded).
+ sum
+ higher
+ True
+
+
+
+ [('state','!=','cancel'),('user_id','=',user.id),('type','=','out_refund')]
+
+
+
+
+
+
+ Monthly Sales Targets
+ monthly
+ ranking
+
+ weekly
+
+
+
+ Lead Acquisition
+ monthly
+ ranking
+
+ weekly
+
+
+
+
+
+ 20000
+
+
+
+
+
+
+ 7
+
+ 1
+
+
+
+ 15
+
+ 2
+
+
+
+ 5
+
+ 3
+
+
+
+
diff --git a/addons/gamification_sale_crm/sale_crm_goals_demo.xml b/addons/gamification_sale_crm/sale_crm_goals_demo.xml
new file mode 100644
index 00000000000..fd69c36162f
--- /dev/null
+++ b/addons/gamification_sale_crm/sale_crm_goals_demo.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+ inprogress
+
+
+
+
+
+
+
+
+
+ 2000
+ inprogress
+
+
+
+
diff --git a/addons/hr/i18n/zh_TW.po b/addons/hr/i18n/zh_TW.po
index e31a3c399fd..00daa2f9477 100644
--- a/addons/hr/i18n/zh_TW.po
+++ b/addons/hr/i18n/zh_TW.po
@@ -7,24 +7,24 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
-"PO-Revision-Date: 2012-05-10 17:48+0000\n"
-"Last-Translator: Fabien (Open ERP) \n"
+"PO-Revision-Date: 2013-12-27 05:02+0000\n"
+"Last-Translator: Andy Cheng \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2013-09-12 05:56+0000\n"
-"X-Generator: Launchpad (build 16761)\n"
+"X-Launchpad-Export-Date: 2013-12-28 05:19+0000\n"
+"X-Generator: Launchpad (build 16877)\n"
#. module: hr
#: model:process.node,name:hr.process_node_openerpuser0
msgid "Openerp user"
-msgstr ""
+msgstr "Openerp 使用者"
#. module: hr
#: field:hr.config.settings,module_hr_timesheet_sheet:0
msgid "Allow timesheets validation by managers"
-msgstr ""
+msgstr "允許主管批准工時表"
#. module: hr
#: field:hr.job,requirements:0
@@ -62,7 +62,7 @@ msgstr ""
#. module: hr
#: view:hr.config.settings:0
msgid "Time Tracking"
-msgstr ""
+msgstr "追蹤時間"
#. module: hr
#: view:hr.employee:0
@@ -73,17 +73,17 @@ msgstr "分組根據..."
#. module: hr
#: model:ir.actions.act_window,name:hr.view_department_form_installer
msgid "Create Your Departments"
-msgstr ""
+msgstr "建立您的部門"
#. module: hr
#: help:hr.job,no_of_employee:0
msgid "Number of employees currently occupying this job position."
-msgstr ""
+msgstr "從事此職位的現有員工數量。"
#. module: hr
#: field:hr.config.settings,module_hr_evaluation:0
msgid "Organize employees periodic evaluation"
-msgstr ""
+msgstr "安排員工定期考評"
#. module: hr
#: view:hr.department:0
@@ -98,19 +98,19 @@ msgstr "部門"
#. module: hr
#: field:hr.employee,work_email:0
msgid "Work Email"
-msgstr ""
+msgstr "工作電子郵件"
#. module: hr
#: help:hr.employee,image:0
msgid ""
"This field holds the image used as photo for the employee, limited to "
"1024x1024px."
-msgstr ""
+msgstr "此欄位存放圖片作為員工照片,限制大小為 1024*1024 像素。"
#. module: hr
#: help:hr.config.settings,module_hr_holidays:0
msgid "This installs the module hr_holidays."
-msgstr ""
+msgstr "此功能將安裝 hr_holidays 模組。"
#. module: hr
#: view:hr.job:0
@@ -125,7 +125,7 @@ msgstr "正招聘"
#. module: hr
#: field:hr.job,message_unread:0
msgid "Unread Messages"
-msgstr ""
+msgstr "未讀訊息"
#. module: hr
#: field:hr.department,company_id:0
@@ -143,7 +143,7 @@ msgstr ""
#. module: hr
#: field:res.users,employee_ids:0
msgid "Related employees"
-msgstr ""
+msgstr "相關員工"
#. module: hr
#: constraint:hr.employee.category:0
@@ -153,28 +153,28 @@ msgstr ""
#. module: hr
#: help:hr.config.settings,module_hr_recruitment:0
msgid "This installs the module hr_recruitment."
-msgstr ""
+msgstr "此功能將安裝 hr_recruitment 模組。"
#. module: hr
#: view:hr.employee:0
msgid "Birth"
-msgstr ""
+msgstr "生日"
#. module: hr
#: model:ir.actions.act_window,name:hr.open_view_categ_form
#: model:ir.ui.menu,name:hr.menu_view_employee_category_form
msgid "Employee Tags"
-msgstr ""
+msgstr "員工標籤"
#. module: hr
#: view:hr.job:0
msgid "Launch Recruitement"
-msgstr ""
+msgstr "啟動招募"
#. module: hr
#: model:process.transition,name:hr.process_transition_employeeuser0
msgid "Link a user to an employee"
-msgstr ""
+msgstr "將一個使用者帳號連結到一位員工"
#. module: hr
#: field:hr.department,parent_id:0
@@ -184,7 +184,7 @@ msgstr "上級部門"
#. module: hr
#: model:ir.ui.menu,name:hr.menu_open_view_attendance_reason_config
msgid "Leaves"
-msgstr ""
+msgstr "休假"
#. module: hr
#: selection:hr.employee,marital:0
@@ -194,37 +194,37 @@ msgstr "已婚"
#. module: hr
#: field:hr.job,message_ids:0
msgid "Messages"
-msgstr ""
+msgstr "訊息"
#. module: hr
#: view:hr.config.settings:0
msgid "Talent Management"
-msgstr ""
+msgstr "人才管理"
#. module: hr
#: help:hr.config.settings,module_hr_timesheet_sheet:0
msgid "This installs the module hr_timesheet_sheet."
-msgstr ""
+msgstr "此功能將安裝 hr_timesheet_sheet 模組。"
#. module: hr
#: view:hr.employee:0
msgid "Mobile:"
-msgstr ""
+msgstr "行動電話:"
#. module: hr
#: view:hr.employee:0
msgid "Position"
-msgstr ""
+msgstr "職位"
#. module: hr
#: help:hr.job,message_unread:0
msgid "If checked new messages require your attention."
-msgstr ""
+msgstr "當有新訊息時通知您。"
#. module: hr
#: field:hr.employee,color:0
msgid "Color Index"
-msgstr ""
+msgstr "顏色索引"
#. module: hr
#: model:process.transition,note:hr.process_transition_employeeuser0
@@ -236,7 +236,7 @@ msgstr ""
#. module: hr
#: field:hr.employee,image_medium:0
msgid "Medium-sized photo"
-msgstr ""
+msgstr "中等尺寸照片"
#. module: hr
#: field:hr.employee,identification_id:0
@@ -251,7 +251,7 @@ msgstr "女"
#. module: hr
#: model:ir.ui.menu,name:hr.menu_open_view_attendance_reason_new_config
msgid "Attendance"
-msgstr ""
+msgstr "出勤"
#. module: hr
#: field:hr.employee,work_phone:0
@@ -277,7 +277,7 @@ msgstr "辦公室位置"
#. module: hr
#: field:hr.job,message_follower_ids:0
msgid "Followers"
-msgstr ""
+msgstr "關注者"
#. module: hr
#: view:hr.employee:0
@@ -307,12 +307,12 @@ msgstr "出生日期"
#. module: hr
#: help:hr.job,no_of_recruitment:0
msgid "Number of new employees you expect to recruit."
-msgstr ""
+msgstr "您期望聘雇的新員工數量。"
#. module: hr
#: model:ir.actions.client,name:hr.action_client_hr_menu
msgid "Open HR Menu"
-msgstr ""
+msgstr "開啟人資選單"
#. module: hr
#: help:hr.job,message_summary:0
@@ -326,12 +326,12 @@ msgstr ""
msgid ""
"This installs the module account_analytic_analysis, which will install sales "
"management too."
-msgstr ""
+msgstr "此功能將安裝 account_analytic_analysis 模組,同時也安裝業務銷售管理模組。"
#. module: hr
#: view:board.board:0
msgid "Human Resources Dashboard"
-msgstr ""
+msgstr "人力資源儀表板"
#. module: hr
#: view:hr.employee:0
@@ -343,7 +343,7 @@ msgstr "工作"
#. module: hr
#: field:hr.job,no_of_employee:0
msgid "Current Number of Employees"
-msgstr ""
+msgstr "現有員工數目"
#. module: hr
#: field:hr.department,member_ids:0
@@ -353,22 +353,22 @@ msgstr "成員"
#. module: hr
#: model:ir.ui.menu,name:hr.menu_hr_configuration
msgid "Configuration"
-msgstr ""
+msgstr "組態設定"
#. module: hr
#: model:process.node,note:hr.process_node_employee0
msgid "Employee form and structure"
-msgstr ""
+msgstr "員工表單與架構"
#. module: hr
#: field:hr.config.settings,module_hr_expense:0
msgid "Manage employees expenses"
-msgstr ""
+msgstr "管理員工費用報支"
#. module: hr
#: view:hr.employee:0
msgid "Tel:"
-msgstr ""
+msgstr "電話:"
#. module: hr
#: selection:hr.employee,marital:0
@@ -430,7 +430,7 @@ msgstr ""
#. module: hr
#: help:hr.config.settings,module_hr_evaluation:0
msgid "This installs the module hr_evaluation."
-msgstr ""
+msgstr "此功能將安裝 hr_evaluation 模組。"
#. module: hr
#: constraint:hr.employee:0
@@ -440,12 +440,12 @@ msgstr ""
#. module: hr
#: help:hr.config.settings,module_hr_attendance:0
msgid "This installs the module hr_attendance."
-msgstr ""
+msgstr "此功能將安裝 hr_attendance 模組。"
#. module: hr
#: field:hr.employee,image_small:0
msgid "Smal-sized photo"
-msgstr ""
+msgstr "小尺吋照片"
#. module: hr
#: view:hr.employee.category:0
@@ -456,22 +456,22 @@ msgstr "員工分類"
#. module: hr
#: field:hr.employee,category_ids:0
msgid "Tags"
-msgstr ""
+msgstr "標籤"
#. module: hr
#: help:hr.config.settings,module_hr_contract:0
msgid "This installs the module hr_contract."
-msgstr ""
+msgstr "此功能將安裝 hr_contract 模組。"
#. module: hr
#: view:hr.employee:0
msgid "Related User"
-msgstr ""
+msgstr "相關使用者"
#. module: hr
#: view:hr.config.settings:0
msgid "or"
-msgstr ""
+msgstr "或"
#. module: hr
#: field:hr.employee.category,name:0
@@ -481,12 +481,12 @@ msgstr "分類"
#. module: hr
#: view:hr.job:0
msgid "Stop Recruitment"
-msgstr ""
+msgstr "停止招募"
#. module: hr
#: field:hr.config.settings,module_hr_attendance:0
msgid "Install attendances feature"
-msgstr ""
+msgstr "安裝出勤管理功能"
#. module: hr
#: help:hr.employee,bank_account_id:0
@@ -501,7 +501,7 @@ msgstr "備註"
#. module: hr
#: model:ir.actions.act_window,name:hr.open_view_employee_tree
msgid "Employees Structure"
-msgstr ""
+msgstr "員工架構"
#. module: hr
#: view:hr.employee:0
@@ -511,7 +511,7 @@ msgstr "聯絡資料"
#. module: hr
#: field:hr.config.settings,module_hr_holidays:0
msgid "Manage holidays, leaves and allocation requests"
-msgstr ""
+msgstr "管理假日、請假及排假。"
#. module: hr
#: field:hr.department,child_ids:0
@@ -528,7 +528,7 @@ msgstr "狀況"
#. module: hr
#: field:hr.employee,otherid:0
msgid "Other Id"
-msgstr ""
+msgstr "其他ID"
#. module: hr
#: model:process.process,name:hr.process_process_employeecontractprocess0
@@ -538,12 +538,12 @@ msgstr "僱傭合約"
#. module: hr
#: view:hr.config.settings:0
msgid "Contracts"
-msgstr ""
+msgstr "合約"
#. module: hr
#: help:hr.job,message_ids:0
msgid "Messages and communication history"
-msgstr ""
+msgstr "訊息及聯絡紀錄"
#. module: hr
#: field:hr.employee,ssnid:0
@@ -553,12 +553,12 @@ msgstr "社會保障號碼(美國)"
#. module: hr
#: field:hr.job,message_is_follower:0
msgid "Is a Follower"
-msgstr ""
+msgstr "為關注者"
#. module: hr
#: field:hr.config.settings,module_hr_recruitment:0
msgid "Manage the recruitment process"
-msgstr ""
+msgstr "管理招募流程"
#. module: hr
#: view:hr.employee:0
@@ -568,12 +568,12 @@ msgstr "活躍"
#. module: hr
#: view:hr.config.settings:0
msgid "Human Resources Management"
-msgstr ""
+msgstr "人力資源管理"
#. module: hr
#: view:hr.config.settings:0
msgid "Install your country's payroll"
-msgstr ""
+msgstr "安裝您國家的薪資模組"
#. module: hr
#: field:hr.employee,bank_account_id:0
@@ -588,7 +588,7 @@ msgstr "公司"
#. module: hr
#: field:hr.job,message_summary:0
msgid "Summary"
-msgstr ""
+msgstr "摘要"
#. module: hr
#: model:process.transition,note:hr.process_transition_contactofemployee0
@@ -615,12 +615,12 @@ msgstr ""
#. module: hr
#: view:hr.employee:0
msgid "HR Settings"
-msgstr ""
+msgstr "人資設定"
#. module: hr
#: view:hr.employee:0
msgid "Citizenship & Other Info"
-msgstr ""
+msgstr "公民與其他資訊"
#. module: hr
#: constraint:hr.department:0
@@ -635,7 +635,7 @@ msgstr "辦公地址"
#. module: hr
#: view:hr.employee:0
msgid "Public Information"
-msgstr ""
+msgstr "公開資訊"
#. module: hr
#: field:hr.employee,marital:0
diff --git a/addons/hr/res_config.py b/addons/hr/res_config.py
index 147c4e4d5b9..94c029262ff 100644
--- a/addons/hr/res_config.py
+++ b/addons/hr/res_config.py
@@ -42,6 +42,8 @@ class hr_config_settings(osv.osv_memory):
help ="""This installs the module hr_contract."""),
'module_hr_evaluation': fields.boolean('Organize employees periodic evaluation',
help ="""This installs the module hr_evaluation."""),
+ 'module_hr_gamification': fields.boolean('Drive engagement with challenges and badges',
+ help ="""This installs the module hr_gamification."""),
'module_account_analytic_analysis': fields.boolean('Allow invoicing based on timesheets (the sale application will be installed)',
help ="""This installs the module account_analytic_analysis, which will install sales management too."""),
'module_hr_payroll': fields.boolean('Manage payroll',
diff --git a/addons/hr/res_config_view.xml b/addons/hr/res_config_view.xml
index 632ceb56918..3dc1795139c 100644
--- a/addons/hr/res_config_view.xml
+++ b/addons/hr/res_config_view.xml
@@ -56,6 +56,10 @@
+
+
+
+
diff --git a/addons/hr/res_users.py b/addons/hr/res_users.py
index c3550596281..d5bebe05446 100644
--- a/addons/hr/res_users.py
+++ b/addons/hr/res_users.py
@@ -57,9 +57,10 @@ class res_users(osv.Model):
various mailboxes, we do not have access to the current partner_id. """
if kwargs.get('type') == 'email':
return super(res_users, self).message_post(cr, uid, thread_id, context=context, **kwargs)
+ res = None
employee_ids = self._message_post_get_eid(cr, uid, thread_id, context=context)
- if not employee_ids:
- pass # dpo something
+ if not employee_ids: # no employee: fall back on previous behavior
+ return super(res_users, self).message_post(cr, uid, thread_id, context=context, **kwargs)
for employee_id in employee_ids:
res = self.pool.get('hr.employee').message_post(cr, uid, employee_id, context=context, **kwargs)
return res
diff --git a/addons/hr/static/src/css/hr.css b/addons/hr/static/src/css/hr.css
index 951ec4d02e3..be21ca7e7a6 100644
--- a/addons/hr/static/src/css/hr.css
+++ b/addons/hr/static/src/css/hr.css
@@ -72,4 +72,4 @@
.openerp .oe_employee_vignette .oe_followers {
width: auto;
float: none;
-}
+}
\ No newline at end of file
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 @@
+
+
+
+
+
+ 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
+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_evaluation/hr_evaluation.py b/addons/hr_evaluation/hr_evaluation.py
index 09c47b89153..462154e9e4a 100644
--- a/addons/hr_evaluation/hr_evaluation.py
+++ b/addons/hr_evaluation/hr_evaluation.py
@@ -101,6 +101,8 @@ Thanks,
class hr_employee(osv.osv):
_name = "hr.employee"
_inherit="hr.employee"
+
+
_columns = {
'evaluation_plan_id': fields.many2one('hr_evaluation.plan', 'Appraisal Plan'),
'evaluation_date': fields.date('Next Appraisal Date', help="The date of the next appraisal is computed by the appraisal plan's dates (first appraisal + periodicity)."),
@@ -123,6 +125,8 @@ class hr_employee(osv.osv):
obj_evaluation.button_plan_in_progress(cr, uid, [plan_id], context=context)
return True
+
+
class hr_evaluation(osv.osv):
_name = "hr_evaluation.evaluation"
_inherit = "mail.thread"
diff --git a/addons/hr_evaluation/security/ir.model.access.csv b/addons/hr_evaluation/security/ir.model.access.csv
index a304ee84c10..4be3d74b7d8 100644
--- a/addons/hr_evaluation/security/ir.model.access.csv
+++ b/addons/hr_evaluation/security/ir.model.access.csv
@@ -39,4 +39,4 @@ access_survey_response_hr_employee,survey.response.employee,survey.model_survey_
access_survey_question_column_heading_hr_employee,survey.question.column.heading.employee,survey.model_survey_question_column_heading,base.group_user,1,0,0,0
access_survey_response_line_hr_employee,survey.response.line.employee,survey.model_survey_response_line,base.group_user,1,1,1,0
access_survey_response_answer_hr_employee,survey.response.answer.hr.employee,survey.model_survey_response_answer,base.group_user,1,1,1,0
-access_survey_tbl_column_heading_hr_employee,survey.tbl.column.heading,survey.model_survey_tbl_column_heading,base.group_user,1,1,1,0
+access_survey_tbl_column_heading_hr_employee,survey.tbl.column.heading,survey.model_survey_tbl_column_heading,base.group_user,1,1,1,0
\ No newline at end of file
diff --git a/addons/hr_expense/i18n/zh_TW.po b/addons/hr_expense/i18n/zh_TW.po
index 29cf442efbc..227f7fd91db 100644
--- a/addons/hr_expense/i18n/zh_TW.po
+++ b/addons/hr_expense/i18n/zh_TW.po
@@ -7,57 +7,57 @@ msgstr ""
"Project-Id-Version: OpenERP Server 5.0.4\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
-"PO-Revision-Date: 2009-01-30 13:19+0000\n"
-"Last-Translator: <>\n"
+"PO-Revision-Date: 2013-12-27 04:48+0000\n"
+"Last-Translator: Andy Cheng \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2013-09-12 05:53+0000\n"
-"X-Generator: Launchpad (build 16761)\n"
+"X-Launchpad-Export-Date: 2013-12-28 05:19+0000\n"
+"X-Generator: Launchpad (build 16877)\n"
#. module: hr_expense
#: view:hr.expense.expense:0
#: model:process.node,name:hr_expense.process_node_confirmedexpenses0
msgid "Confirmed Expenses"
-msgstr ""
+msgstr "已確認費用報支"
#. module: hr_expense
#: model:ir.model,name:hr_expense.model_hr_expense_line
msgid "Expense Line"
-msgstr ""
+msgstr "費用報支項目"
#. module: hr_expense
#: model:process.node,note:hr_expense.process_node_reimbursement0
msgid "The accoutant reimburse the expenses"
-msgstr ""
+msgstr "會計師報支費用"
#. module: hr_expense
#: model:mail.message.subtype,description:hr_expense.mt_expense_approved
msgid "Expense approved"
-msgstr ""
+msgstr "費用報支已核准"
#. module: hr_expense
#: field:hr.expense.expense,date_confirm:0
#: field:hr.expense.report,date_confirm:0
msgid "Confirmation Date"
-msgstr ""
+msgstr "確認日期"
#. module: hr_expense
#: view:hr.expense.expense:0
#: view:hr.expense.report:0
msgid "Group By..."
-msgstr ""
+msgstr "分組..."
#. module: hr_expense
#: model:product.template,name:hr_expense.air_ticket_product_template
msgid "Air Ticket"
-msgstr ""
+msgstr "機票"
#. module: hr_expense
#: report:hr.expense:0
msgid "Validated By"
-msgstr ""
+msgstr "已完成審核"
#. module: hr_expense
#: view:hr.expense.expense:0
@@ -65,45 +65,45 @@ msgstr ""
#: view:hr.expense.report:0
#: field:hr.expense.report,department_id:0
msgid "Department"
-msgstr ""
+msgstr "部門"
#. module: hr_expense
#: view:hr.expense.expense:0
msgid "New Expense"
-msgstr ""
+msgstr "新增費用報支"
#. module: hr_expense
#: field:hr.expense.line,uom_id:0
#: view:product.product:0
msgid "Unit of Measure"
-msgstr ""
+msgstr "度量單位"
#. module: hr_expense
#: selection:hr.expense.report,month:0
msgid "March"
-msgstr ""
+msgstr "三月"
#. module: hr_expense
#: field:hr.expense.expense,message_unread:0
msgid "Unread Messages"
-msgstr ""
+msgstr "未讀訊息"
#. module: hr_expense
#: field:hr.expense.expense,company_id:0
#: view:hr.expense.report:0
#: field:hr.expense.report,company_id:0
msgid "Company"
-msgstr ""
+msgstr "公司"
#. module: hr_expense
#: view:hr.expense.expense:0
msgid "Set to Draft"
-msgstr ""
+msgstr "設為草稿"
#. module: hr_expense
#: view:hr.expense.expense:0
msgid "To Pay"
-msgstr ""
+msgstr "應付款"
#. module: hr_expense
#: code:addons/hr_expense/hr_expense.py:172
@@ -111,12 +111,12 @@ msgstr ""
msgid ""
"No expense journal found. Please make sure you have a journal with type "
"'purchase' configured."
-msgstr ""
+msgstr "未找到費用日記帳。請確認您是否有設置型態為「採購」的日記帳。"
#. module: hr_expense
#: model:ir.model,name:hr_expense.model_hr_expense_report
msgid "Expenses Statistics"
-msgstr ""
+msgstr "費用報支統計"
#. module: hr_expense
#: view:hr.expense.expense:0
@@ -127,7 +127,7 @@ msgstr ""
#: view:hr.expense.report:0
#: field:hr.expense.report,day:0
msgid "Day"
-msgstr ""
+msgstr "日"
#. module: hr_expense
#: help:hr.expense.expense,date_valid:0
@@ -139,12 +139,12 @@ msgstr ""
#. module: hr_expense
#: view:hr.expense.expense:0
msgid "Notes"
-msgstr ""
+msgstr "備註"
#. module: hr_expense
#: field:hr.expense.expense,message_ids:0
msgid "Messages"
-msgstr ""
+msgstr "訊息"
#. module: hr_expense
#: code:addons/hr_expense/hr_expense.py:172
@@ -154,28 +154,28 @@ msgstr ""
#: code:addons/hr_expense/hr_expense.py:353
#, python-format
msgid "Error!"
-msgstr ""
+msgstr "錯誤!"
#. module: hr_expense
#: model:mail.message.subtype,description:hr_expense.mt_expense_refused
msgid "Expense refused"
-msgstr ""
+msgstr "費用報支被拒"
#. module: hr_expense
#: model:ir.actions.act_window,name:hr_expense.hr_expense_product
#: view:product.product:0
msgid "Products"
-msgstr ""
+msgstr "產品"
#. module: hr_expense
#: view:hr.expense.report:0
msgid "Confirm Expenses"
-msgstr ""
+msgstr "確認費用報支"
#. module: hr_expense
#: selection:hr.expense.report,state:0
msgid "Cancelled"
-msgstr ""
+msgstr "已取消"
#. module: hr_expense
#: model:process.node,note:hr_expense.process_node_refused0
@@ -190,17 +190,17 @@ msgstr ""
#. module: hr_expense
#: selection:hr.expense.report,state:0
msgid "Waiting confirmation"
-msgstr ""
+msgstr "等待確認中"
#. module: hr_expense
#: selection:hr.expense.report,state:0
msgid "Accepted"
-msgstr ""
+msgstr "已接受"
#. module: hr_expense
#: field:hr.expense.line,ref:0
msgid "Reference"
-msgstr ""
+msgstr "參考"
#. module: hr_expense
#: report:hr.expense:0
@@ -228,7 +228,7 @@ msgstr ""
#: view:hr.expense.report:0
#: field:hr.expense.report,nbr:0
msgid "# of Lines"
-msgstr ""
+msgstr "項目數量"
#. module: hr_expense
#: help:hr.expense.expense,message_summary:0
@@ -241,33 +241,33 @@ msgstr ""
#: code:addons/hr_expense/hr_expense.py:453
#, python-format
msgid "Warning"
-msgstr ""
+msgstr "警告"
#. module: hr_expense
#: report:hr.expense:0
msgid "(Date and signature)"
-msgstr ""
+msgstr "(日期與簽名)"
#. module: hr_expense
#: report:hr.expense:0
msgid "Total:"
-msgstr ""
+msgstr "合計:"
#. module: hr_expense
#: model:process.transition,name:hr_expense.process_transition_refuseexpense0
msgid "Refuse expense"
-msgstr ""
+msgstr "拒絕費用報支"
#. module: hr_expense
#: field:hr.expense.report,price_average:0
msgid "Average Price"
-msgstr ""
+msgstr "平均價格"
#. module: hr_expense
#: view:hr.expense.expense:0
#: model:process.transition.action,name:hr_expense.process_transition_action_confirm0
msgid "Confirm"
-msgstr ""
+msgstr "確認"
#. module: hr_expense
#: model:process.node,note:hr_expense.process_node_supplierinvoice0
diff --git a/addons/hr_gamification/__init__.py b/addons/hr_gamification/__init__.py
new file mode 100644
index 00000000000..1896704c7fc
--- /dev/null
+++ b/addons/hr_gamification/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 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
+import wizard
\ No newline at end of file
diff --git a/addons/hr_gamification/__openerp__.py b/addons/hr_gamification/__openerp__.py
new file mode 100644
index 00000000000..c75a4454437
--- /dev/null
+++ b/addons/hr_gamification/__openerp__.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 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 .
+#
+##############################################################################
+{
+ 'name': 'HR Gamification',
+ 'version': '1.0',
+ 'author': 'OpenERP SA',
+ 'category': 'hidden',
+ 'depends': ['gamification', 'hr'],
+ 'description': """Use the HR ressources for the gamification process.
+
+The HR officer can now manage challenges and badges.
+This allow the user to send badges to employees instead of simple users.
+Badge received are displayed on the user profile.
+""",
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'security/gamification_security.xml',
+ 'wizard/grant_badge.xml',
+ 'views/gamification.xml',
+ ],
+ 'js': ['static/src/js/gamification.js'],
+}
diff --git a/addons/hr_gamification/models/__init__.py b/addons/hr_gamification/models/__init__.py
new file mode 100644
index 00000000000..51cf01dfa09
--- /dev/null
+++ b/addons/hr_gamification/models/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 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 gamification
diff --git a/addons/hr_gamification/models/gamification.py b/addons/hr_gamification/models/gamification.py
new file mode 100644
index 00000000000..b2aa9039cb1
--- /dev/null
+++ b/addons/hr_gamification/models/gamification.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from openerp.osv import fields, osv
+
+
+class hr_gamification_badge_user(osv.Model):
+ """User having received a badge"""
+
+ _name = 'gamification.badge.user'
+ _inherit = ['gamification.badge.user']
+
+ _columns = {
+ 'employee_id': fields.many2one("hr.employee", string='Employee'),
+ }
+
+ def _check_employee_related_user(self, cr, uid, ids, context=None):
+ for badge_user in self.browse(cr, uid, ids, context=context):
+ if badge_user.user_id and badge_user.employee_id:
+ if badge_user.employee_id not in badge_user.user_id.employee_ids:
+ return False
+ return True
+
+ _constraints = [
+ (_check_employee_related_user, "The selected employee does not correspond to the selected user.", ['employee_id']),
+ ]
+
+
+class gamification_badge(osv.Model):
+ _name = 'gamification.badge'
+ _inherit = ['gamification.badge']
+
+ def get_granted_employees(self, cr, uid, badge_ids, context=None):
+ if context is None:
+ context = {}
+
+ employee_ids = []
+ badge_user_ids = self.pool.get('gamification.badge.user').search(cr, uid, [('badge_id', 'in', badge_ids), ('employee_id', '!=', False)], context=context)
+ for badge_user in self.pool.get('gamification.badge.user').browse(cr, uid, badge_user_ids, context):
+ employee_ids.append(badge_user.employee_id.id)
+ # remove duplicates
+ employee_ids = list(set(employee_ids))
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': 'Granted Employees',
+ 'view_mode': 'kanban,tree,form',
+ 'view_type': 'form',
+ 'res_model': 'hr.employee',
+ 'domain': [('id', 'in', employee_ids)]
+ }
+
+
+class hr_employee(osv.osv):
+ _name = "hr.employee"
+ _inherit = "hr.employee"
+
+ def _get_employee_goals(self, cr, uid, ids, field_name, arg, context=None):
+ """Return the list of goals assigned to the employee"""
+ res = {}
+ for employee in self.browse(cr, uid, ids, context=context):
+ res[employee.id] = self.pool.get('gamification.goal').search(cr,uid,[('user_id', '=', employee.user_id.id), ('challenge_id.category', '=', 'hr')], context=context)
+ return res
+
+ def _get_employee_badges(self, cr, uid, ids, field_name, arg, context=None):
+ """Return the list of badge_users assigned to the employee"""
+ res = {}
+ for employee in self.browse(cr, uid, ids, context=context):
+ res[employee.id] = self.pool.get('gamification.badge.user').search(cr, uid, [
+ '|',
+ ('employee_id', '=', employee.id),
+ '&',
+ ('employee_id', '=', False),
+ ('user_id', '=', employee.user_id.id)
+ ], context=context)
+ return res
+
+ def _has_badges(self, cr, uid, ids, field_name, arg, context=None):
+ """Return the list of badge_users assigned to the employee"""
+ res = {}
+ for employee in self.browse(cr, uid, ids, context=context):
+ employee_badge_ids = self.pool.get('gamification.badge.user').search(cr, uid, [
+ '|',
+ ('employee_id', '=', employee.id),
+ '&',
+ ('employee_id', '=', False),
+ ('user_id', '=', employee.user_id.id)
+ ], context=context)
+ res[employee.id] = len(employee_badge_ids) > 0
+ return res
+
+ _columns = {
+ 'goal_ids': fields.function(_get_employee_goals, type="one2many", obj='gamification.goal', string="Employee HR Goals"),
+ 'badge_ids': fields.function(_get_employee_badges, type="one2many", obj='gamification.badge.user', string="Employee Badges"),
+ 'has_badges': fields.function(_has_badges, type="boolean", string="Has Badges"),
+ }
diff --git a/addons/hr_gamification/security/gamification_security.xml b/addons/hr_gamification/security/gamification_security.xml
new file mode 100644
index 00000000000..e0f9a6cf918
--- /dev/null
+++ b/addons/hr_gamification/security/gamification_security.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ HR Officer can see any goal
+
+
+
+
+
+
+ [(1, '=', 1)]
+
+
+
+
diff --git a/addons/hr_gamification/security/ir.model.access.csv b/addons/hr_gamification/security/ir.model.access.csv
new file mode 100644
index 00000000000..b89db7cd104
--- /dev/null
+++ b/addons/hr_gamification/security/ir.model.access.csv
@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+challenge_officer,"Challenge Officer",gamification.model_gamification_challenge,base.group_hr_user,1,1,1,1
+challenge_line_officer,"Challenge Line Officer",gamification.model_gamification_challenge_line,base.group_hr_user,1,1,1,1
+badge_officer,"Badge Officer",gamification.model_gamification_badge,base.group_hr_user,1,1,1,1
+badge_user_officer,"Badge-user Officer",gamification.model_gamification_badge_user,base.group_hr_user,1,1,1,1
\ No newline at end of file
diff --git a/addons/hr_gamification/static/src/js/gamification.js b/addons/hr_gamification/static/src/js/gamification.js
new file mode 100644
index 00000000000..a9126464a10
--- /dev/null
+++ b/addons/hr_gamification/static/src/js/gamification.js
@@ -0,0 +1,19 @@
+openerp.hr_gamification = function(instance) {
+ instance.web_kanban.KanbanRecord.include({
+ on_card_clicked: function() {
+ if (this.view.dataset.model === 'gamification.badge.user') {
+ var action = {
+ type: 'ir.actions.act_window',
+ res_model: 'gamification.badge',
+ view_mode: 'form',
+ view_type: 'form,kanban,tree',
+ views: [[false, 'form']],
+ res_id: this.record.badge_id.raw_value[0]
+ };
+ this.do_action(action);
+ } else {
+ this._super.apply(this, arguments);
+ }
+ }
+ });
+};
diff --git a/addons/hr_gamification/views/gamification.xml b/addons/hr_gamification/views/gamification.xml
new file mode 100644
index 00000000000..e2d45948b95
--- /dev/null
+++ b/addons/hr_gamification/views/gamification.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+ Badge Form
+ gamification.badge
+
+
+
+
+
+
+
+
+
+
+
+ hr.hr.employee.view.form
+ hr.employee
+
+
+
+
+
+
+ to reward this employee for a good action
+
+
+ Click to grant this employee his first badge
+
+ Badges are rewards of good work. Give them to people you believe deserve it.
+
+ Assign a list of goals to chosen users to evaluate them.
+ The challenge can use a period (weekly, monthly...) for automatic creation of goals.
+ The goals are created for the specified users or member of the group.
+
+
+
+
+
+ kanban
+
+
+
+
+
+ form
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/hr_gamification/wizard/__init__.py b/addons/hr_gamification/wizard/__init__.py
new file mode 100644
index 00000000000..bb521f011bb
--- /dev/null
+++ b/addons/hr_gamification/wizard/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 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 grant_badge
\ No newline at end of file
diff --git a/addons/hr_gamification/wizard/grant_badge.py b/addons/hr_gamification/wizard/grant_badge.py
new file mode 100644
index 00000000000..b8a51044d4c
--- /dev/null
+++ b/addons/hr_gamification/wizard/grant_badge.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+#
+##############################################################################
+
+from openerp.osv import fields, osv
+from openerp.tools.translate import _
+
+class hr_grant_badge_wizard(osv.TransientModel):
+ _name = 'gamification.badge.user.wizard'
+ _inherit = ['gamification.badge.user.wizard']
+
+ _columns = {
+ 'employee_id': fields.many2one("hr.employee", string='Employee', required=True),
+ 'user_id': fields.related("employee_id", "user_id",
+ type="many2one", relation="res.users",
+ store=True, string='User')
+ }
+
+ def action_grant_badge(self, cr, uid, ids, context=None):
+ """Wizard action for sending a badge to a chosen employee"""
+ if context is None:
+ context = {}
+
+ badge_user_obj = self.pool.get('gamification.badge.user')
+
+ for wiz in self.browse(cr, uid, ids, context=context):
+ if not wiz.user_id:
+ raise osv.except_osv(_('Warning!'), _('You can send badges only to employees linked to a user.'))
+
+ if uid == wiz.user_id.id:
+ raise osv.except_osv(_('Warning!'), _('You can not send a badge to yourself'))
+
+ values = {
+ 'user_id': wiz.user_id.id,
+ 'sender_id': uid,
+ 'badge_id': wiz.badge_id.id,
+ 'employee_id': wiz.employee_id.id,
+ 'comment': wiz.comment,
+ }
+
+ badge_user = badge_user_obj.create(cr, uid, values, context=context)
+ result = badge_user_obj._send_badge(cr, uid, [badge_user], context=context)
+ return result
\ No newline at end of file
diff --git a/addons/hr_gamification/wizard/grant_badge.xml b/addons/hr_gamification/wizard/grant_badge.xml
new file mode 100644
index 00000000000..6dcf2d2e7c4
--- /dev/null
+++ b/addons/hr_gamification/wizard/grant_badge.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+ Grant Badge Employee Form
+ gamification.badge.user.wizard
+
+
+
+
+
+
+
+
+
+
+
+ Reward Employee Badge Form
+ gamification.badge.user.wizard
+
+
+
+
+
+
+
+
+
diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py
index 682508e0ca0..a30b874bd8a 100644
--- a/addons/hr_holidays/hr_holidays.py
+++ b/addons/hr_holidays/hr_holidays.py
@@ -242,6 +242,10 @@ class hr_holidays(osv.osv):
result['value'] = {
'employee_id': ids_employee[0]
}
+ elif holiday_type != 'employee':
+ result['value'] = {
+ 'employee_id': False
+ }
return result
def onchange_employee(self, cr, uid, ids, employee_id):
diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml
index 7642d46273f..af20d727fc8 100644
--- a/addons/hr_holidays/hr_holidays_view.xml
+++ b/addons/hr_holidays/hr_holidays_view.xml
@@ -14,7 +14,7 @@
-
+
@@ -47,61 +47,14 @@
-
+
+ Leave Requesthr.holidays
- 1
-
-
-
-
-
- Allocation Request
- hr.holidays
-
-
diff --git a/addons/hr_recruitment/res_config.py b/addons/hr_recruitment/res_config.py
index d18ca7de477..ca70150b592 100644
--- a/addons/hr_recruitment/res_config.py
+++ b/addons/hr_recruitment/res_config.py
@@ -26,7 +26,7 @@ class hr_applicant_settings(osv.osv_memory):
_inherit = ['hr.config.settings', 'fetchmail.config.settings']
_columns = {
- 'module_document_ftp': fields.boolean('Allow the automatic indexation of resumes',
+ 'module_document': fields.boolean('Allow the automatic indexation of resumes',
help='Manage your CV\'s and motivation letter related to all applicants.\n'
'-This installs the module document_ftp. This will install the knowledge management module in order to allow you to search using specific keywords through the content of all documents (PDF, .DOCx...)'),
'fetchmail_applicants': fields.boolean('Create applicants from an incoming email account',
diff --git a/addons/hr_recruitment/res_config_view.xml b/addons/hr_recruitment/res_config_view.xml
index 1c692099f60..b0dfc5bb232 100644
--- a/addons/hr_recruitment/res_config_view.xml
+++ b/addons/hr_recruitment/res_config_view.xml
@@ -15,14 +15,8 @@
diff --git a/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.py b/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.py
index 9ea0d8aa1c5..0944e89f1de 100644
--- a/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.py
+++ b/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.py
@@ -36,7 +36,7 @@ class MailMassMailingCreate(osv.TransientModel):
required=True,
),
'model_id': fields.many2one(
- 'ir.model', 'Document',
+ 'ir.model', 'Document Type',
required=True,
help='Document on which the mass mailing will run. This must be a '
'valid OpenERP model.',
@@ -116,6 +116,8 @@ class MailMassMailingCreate(osv.TransientModel):
'default_template_id': wizard.template_id.id,
'default_use_mass_mailing_campaign': True,
'default_use_active_domain': True,
+ 'default_model': wizard.model_id.model,
+ 'default_res_id': False,
'default_active_domain': wizard.domain,
'default_mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
'default_mass_mailing_id': wizard.mass_mailing_id.id,
diff --git a/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.xml b/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.xml
index e307bdd8909..b2b48fb8618 100644
--- a/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.xml
+++ b/addons/mass_mailing/wizard/mail_mass_mailing_create_segment.xml
@@ -42,7 +42,7 @@
-
+
-
+
Please choose the name of the mailing.
@@ -76,6 +75,7 @@
formformnew
+ {'default_mass_mailing_campaign_id': active_id}
diff --git a/addons/mrp/report/price.py b/addons/mrp/report/price.py
index 5afd6b3ee49..ff3d5ff5c56 100644
--- a/addons/mrp/report/price.py
+++ b/addons/mrp/report/price.py
@@ -70,6 +70,8 @@ class report_custom(report_rml):
main_strd_price = str(std_price) + '\r\n'
sum_strd = prod_qtty*std_price
for seller_id in prod.seller_ids:
+ if seller_id.name.id == prod.seller_id.id:
+ continue
sellers += '- '+ to_xml(seller_id.name.name) +'\r\n'
pricelist = seller_id.name.property_product_pricelist_purchase
price = pricelist_pool.price_get(cr,uid,[pricelist.id],
diff --git a/addons/note/note.py b/addons/note/note.py
index 00c67c37191..fe917531472 100644
--- a/addons/note/note.py
+++ b/addons/note/note.py
@@ -84,9 +84,10 @@ class note_note(osv.osv):
return ids and ids[0] or False
def _set_stage_per_user(self, cr, uid, id, name, value, args=None, context=None):
- note = self.browse(cr, uid, id, context=context)
- if not value: return False
- stage_ids = [value] + [stage.id for stage in note.stage_ids if stage.user_id.id != uid ]
+ if not value:
+ return False
+ note = self.browse(cr, SUPERUSER_ID, id, context=context) # do it as SUPERUSER because when creating, followers are not necessariliry set (another function field)
+ stage_ids = [value] + [stage.id for stage in note.stage_ids if stage.user_id.id != uid]
return self.write(cr, uid, [id], {'stage_ids': [(6, 0, set(stage_ids))]}, context=context)
def _get_stage_per_user(self, cr, uid, ids, name, args, context=None):
diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py
index 60b9cb25fa3..430789a15fd 100644
--- a/addons/point_of_sale/point_of_sale.py
+++ b/addons/point_of_sale/point_of_sale.py
@@ -723,15 +723,16 @@ class pos_order(osv.osv):
}, context=context)
self.write(cr, uid, [order.id], {'picking_id': picking_id}, context=context)
location_id = picking_type.default_location_src_id.id
- output_id = picking_type.default_location_dest_id.id
- if not location_id or not output_id:
+ if order.partner_id:
+ destination_id = order.partner_id.property_stock_customer.id
+ else:
+ destination_id = picking_type.default_location_dest_id.id
+ if not location_id or not destination_id:
raise osv.except_osv(_('Error!'), _('Missing source or destination location for picking type %s. Please configure those fields and try again.' % (picking_type.name,)))
for line in order.lines:
if line.product_id and line.product_id.type == 'service':
continue
- if line.qty < 0:
- location_id, output_id = output_id, location_id
move_obj.create(cr, uid, {
'name': line.name,
@@ -742,12 +743,10 @@ class pos_order(osv.osv):
'product_uos_qty': abs(line.qty),
'product_uom_qty': abs(line.qty),
'state': 'draft',
- 'location_id': location_id,
- 'location_dest_id': output_id,
+ 'location_id': location_id if line.qty >= 0 else destination_id,
+ 'location_dest_id': destination_id if line.qty >= 0 else location_id,
}, context=context)
- if line.qty < 0:
- location_id, output_id = output_id, location_id
- picking_obj.action_confirm(cr, uid, [picking_id])
+ picking_obj.action_confirm(cr, uid, [picking_id], context=context)
picking_obj.force_assign(cr, uid, [picking_id], context=context)
picking_obj.action_done(cr, uid, [picking_id], context=context)
return True
diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js
index 67fde81136a..7071a862413 100644
--- a/addons/point_of_sale/static/src/js/devices.js
+++ b/addons/point_of_sale/static/src/js/devices.js
@@ -560,6 +560,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
this.handler = function(e){
if(e.which === 13){ //ignore returns
+ e.preventDefault();
return;
}
diff --git a/addons/portal_gamification/__init__.py b/addons/portal_gamification/__init__.py
new file mode 100644
index 00000000000..484faca7b68
--- /dev/null
+++ b/addons/portal_gamification/__init__.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 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 .
+#
+##############################################################################
+
diff --git a/addons/portal_gamification/__openerp__.py b/addons/portal_gamification/__openerp__.py
new file mode 100644
index 00000000000..e20e54be6e3
--- /dev/null
+++ b/addons/portal_gamification/__openerp__.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013 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 .
+#
+##############################################################################
+
+
+{
+ 'name': 'Portal Gamification',
+ 'version': '1',
+ 'category': 'Tools',
+ 'complexity': 'easy',
+ 'description': """
+This module adds security rules for gamification to allow portal users to participate to challenges
+===================================================================================================
+ """,
+ 'author': 'OpenERP SA',
+ 'depends': ['gamification','portal'],
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'security/portal_security.xml',
+ ],
+ 'installable': True,
+ 'auto_install': True,
+ 'category': 'Hidden',
+}
+
diff --git a/addons/portal_gamification/security/ir.model.access.csv b/addons/portal_gamification/security/ir.model.access.csv
new file mode 100644
index 00000000000..4f21669cf55
--- /dev/null
+++ b/addons/portal_gamification/security/ir.model.access.csv
@@ -0,0 +1,7 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+goal_portal,"Goal Portal",gamification.model_gamification_goal,portal.group_portal,1,1,0,0
+goal_definition_portal,"Goal Definition Portal",gamification.model_gamification_goal_definition,portal.group_portal,1,0,0,0
+challenge_portal,"Goal Challenge Portal",gamification.model_gamification_challenge,portal.group_portal,1,0,0,0
+challenge_line_portal,"Challenge Line Portal",gamification.model_gamification_challenge_line,portal.group_portal,1,0,0,0
+badge_portal,"Badge Portal",gamification.model_gamification_badge,portal.group_portal,1,0,0,0
+badge_user_portal,"Badge-user Portal",gamification.model_gamification_badge_user,portal.group_portal,1,1,1,0
diff --git a/addons/portal_gamification/security/portal_security.xml b/addons/portal_gamification/security/portal_security.xml
new file mode 100644
index 00000000000..6b46fe96519
--- /dev/null
+++ b/addons/portal_gamification/security/portal_security.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/addons/project/i18n/he.po b/addons/project/i18n/he.po
new file mode 100644
index 00000000000..714693d9df4
--- /dev/null
+++ b/addons/project/i18n/he.po
@@ -0,0 +1,2109 @@
+# Hebrew translation for openobject-addons
+# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:04+0000\n"
+"PO-Revision-Date: 2013-12-27 10:08+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Hebrew \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2013-12-28 05:19+0000\n"
+"X-Generator: Launchpad (build 16877)\n"
+
+#. module: project
+#: view:project.project:0
+msgid "Email Interface"
+msgstr ""
+
+#. module: project
+#: help:account.analytic.account,use_tasks:0
+msgid ""
+"If checked, this contract will be available in the project menu and you will "
+"be able to manage tasks or track issues"
+msgstr ""
+
+#. module: project
+#: field:project.project,progress_rate:0
+#: view:report.project.task.user:0
+#: field:report.project.task.user,progress:0
+msgid "Progress"
+msgstr ""
+
+#. module: project
+#: model:process.node,name:project.process_node_taskbydelegate0
+msgid "Task by delegate"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Parent"
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,name:project.dblc_proj
+msgid "Project's tasks"
+msgstr ""
+
+#. module: project
+#: field:project.task.type,name:0
+msgid "Stage Name"
+msgstr ""
+
+#. module: project
+#: model:process.transition.action,name:project.process_transition_action_openpendingtask0
+msgid "Set pending"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "New Project Based on Template"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+#: field:report.project.task.user,day:0
+msgid "Day"
+msgstr ""
+
+#. module: project
+#: model:project.task.type,name:project.project_tt_merge
+msgid "Merge"
+msgstr ""
+
+#. module: project
+#: view:res.partner:0
+msgid "Start Task"
+msgstr ""
+
+#. module: project
+#: code:addons/project/project.py:944
+#, python-format
+msgid "Warning !"
+msgstr ""
+
+#. module: project
+#: help:project.project,message_unread:0
+#: help:project.task,message_unread:0
+msgid "If checked new messages require your attention."
+msgstr ""
+
+#. module: project
+#: model:process.node,name:project.process_node_donetask0
+msgid "Done task"
+msgstr ""
+
+#. module: project
+#: model:process.node,note:project.process_node_donetask0
+msgid "Task is Completed"
+msgstr ""
+
+#. module: project
+#: view:res.partner:0
+msgid "False"
+msgstr ""
+
+#. module: project
+#: model:project.task.type,name:project.project_tt_testing
+msgid "Testing"
+msgstr ""
+
+#. module: project
+#: model:ir.model,name:project.model_account_analytic_account
+msgid "Analytic Account"
+msgstr ""
+
+#. module: project
+#: field:project.config.settings,group_time_work_estimation_tasks:0
+msgid "Manage time estimation on tasks"
+msgstr ""
+
+#. module: project
+#: help:project.project,message_summary:0
+#: help:project.task,message_summary:0
+msgid ""
+"Holds the Chatter summary (number of messages, ...). This summary is "
+"directly in html format in order to be inserted in kanban views."
+msgstr ""
+
+#. module: project
+#: code:addons/project/project.py:444
+#: code:addons/project/project.py:1332
+#, python-format
+msgid "Warning!"
+msgstr ""
+
+#. module: project
+#: model:ir.model,name:project.model_res_partner
+msgid "Partner"
+msgstr ""
+
+#. module: project
+#: field:project.config.settings,group_manage_delegation_task:0
+msgid "Allow task delegation"
+msgstr ""
+
+#. module: project
+#: field:project.task.delegate,planned_hours:0
+#: view:report.project.task.user:0
+#: field:report.project.task.user,hours_planned:0
+msgid "Planned Hours"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Reset as Project"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+msgid "In progress tasks"
+msgstr ""
+
+#. module: project
+#: help:project.project,progress_rate:0
+msgid "Percent of tasks closed according to the total of tasks todo."
+msgstr ""
+
+#. module: project
+#: model:ir.actions.client,name:project.action_client_project_menu
+msgid "Open Project Menu"
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,help:project.action_project_task_user_tree
+msgid ""
+"This report allows you to analyse the performance of your projects and "
+"users. You can analyse the quantities of tasks, the hours spent compared to "
+"the planned hours, the average number of days to open or close a task, etc."
+msgstr ""
+
+#. module: project
+#: view:project.task.delegate:0
+msgid "Validation Task Title"
+msgstr ""
+
+#. module: project
+#: model:res.groups,name:project.group_delegate_task
+msgid "Task Delegation"
+msgstr ""
+
+#. module: project
+#: field:project.project,planned_hours:0
+#: field:project.task.history,planned_hours:0
+#: field:project.task.history.cumulative,planned_hours:0
+msgid "Planned Time"
+msgstr ""
+
+#. module: project
+#: selection:project.project,privacy_visibility:0
+msgid "Public"
+msgstr ""
+
+#. module: project
+#: model:project.category,name:project.project_category_01
+msgid "Contact's suggestion"
+msgstr ""
+
+#. module: project
+#: help:project.config.settings,group_time_work_estimation_tasks:0
+msgid "Allows you to compute Time Estimation on tasks."
+msgstr ""
+
+#. module: project
+#: field:report.project.task.user,user_id:0
+msgid "Assigned To"
+msgstr ""
+
+#. module: project
+#: model:mail.message.subtype,name:project.mt_project_task_closed
+#: model:mail.message.subtype,name:project.mt_task_closed
+msgid "Task Done"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+#: view:report.project.task.user:0
+#: field:report.project.task.user,partner_id:0
+msgid "Contact"
+msgstr ""
+
+#. module: project
+#: model:process.transition,name:project.process_transition_delegate0
+#: view:project.task:0
+msgid "Delegate"
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,name:project.open_view_template_project
+#: view:project.project:0
+msgid "Templates of Projects"
+msgstr ""
+
+#. module: project
+#: field:project.project,analytic_account_id:0
+msgid "Contract/Analytic"
+msgstr ""
+
+#. module: project
+#: view:project.config.settings:0
+msgid "Project Management"
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,name:project.action_project_task_delegate
+#: view:project.task.delegate:0
+msgid "Project Task Delegate"
+msgstr ""
+
+#. module: project
+#: model:mail.message.subtype,name:project.mt_project_task_started
+#: model:mail.message.subtype,name:project.mt_task_started
+msgid "Task Started"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Very Important"
+msgstr ""
+
+#. module: project
+#: view:project.config.settings:0
+msgid "Support"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Member"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Cancel Task"
+msgstr ""
+
+#. module: project
+#: help:project.project,members:0
+msgid ""
+"Project's members are users who can have an access to the tasks related to "
+"this project."
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+#: field:project.task,manager_id:0
+msgid "Project Manager"
+msgstr ""
+
+#. module: project
+#: field:project.project,state:0
+#: field:project.task,state:0
+#: field:project.task.history,state:0
+#: field:project.task.history.cumulative,state:0
+#: field:report.project.task.user,state:0
+msgid "Status"
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,month:0
+msgid "August"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+#: field:project.project,complete_name:0
+msgid "Project Name"
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,month:0
+msgid "June"
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,month:0
+msgid "October"
+msgstr ""
+
+#. module: project
+#: help:project.project,total_hours:0
+msgid ""
+"Sum of total hours of all tasks related to this project and its child "
+"projects."
+msgstr ""
+
+#. module: project
+#: help:project.project,active:0
+msgid ""
+"If the active field is set to False, it will allow you to hide the project "
+"without removing it."
+msgstr ""
+
+#. module: project
+#: model:process.transition,note:project.process_transition_opendonetask0
+msgid "When task is completed, it will come into the done state."
+msgstr ""
+
+#. module: project
+#: field:project.project,message_summary:0
+#: field:project.task,message_summary:0
+msgid "Summary"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Append this project to another one using analytic accounts hierarchy"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: view:project.task.history.cumulative:0
+msgid "In Progress Tasks"
+msgstr ""
+
+#. module: project
+#: help:res.company,project_time_mode_id:0
+msgid ""
+"This will set the unit of measure used in projects and tasks.\n"
+"If you use the timesheet linked to projects (project_timesheet module), "
+"don't forget to setup the right unit of measure in your employees."
+msgstr ""
+
+#. module: project
+#: field:project.task,user_id:0
+#: view:report.project.task.user:0
+msgid "Assigned to"
+msgstr ""
+
+#. module: project
+#: code:addons/project/project.py:1033
+#, python-format
+msgid "Delegated User should be specified"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Project(s) Manager"
+msgstr ""
+
+#. module: project
+#: selection:project.project,state:0
+#: view:project.task:0
+#: selection:project.task,state:0
+#: selection:project.task.history,state:0
+#: selection:project.task.history.cumulative,state:0
+#: selection:project.task.type,state:0
+#: selection:report.project.task.user,state:0
+msgid "In Progress"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Reactivate"
+msgstr ""
+
+#. module: project
+#: field:project.project,resource_calendar_id:0
+msgid "Working Time"
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,name:project.action_project_task_reevaluate
+msgid "Re-evaluate Task"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Validate planned time"
+msgstr ""
+
+#. module: project
+#: field:project.config.settings,module_pad:0
+msgid "Use integrated collaborative note pads on task"
+msgstr ""
+
+#. module: project
+#: model:mail.message.subtype,name:project.mt_project_task_blocked
+#: model:mail.message.subtype,name:project.mt_task_blocked
+msgid "Task Blocked"
+msgstr ""
+
+#. module: project
+#: model:process.node,note:project.process_node_opentask0
+msgid "Encode your working hours."
+msgstr ""
+
+#. module: project
+#: field:project.project,alias_id:0
+msgid "Alias"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "oe_kanban_text_red"
+msgstr ""
+
+#. module: project
+#: model:mail.message.subtype,description:project.mt_task_blocked
+msgid "Task blocked"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Delegation"
+msgstr ""
+
+#. module: project
+#: field:project.task,create_date:0
+msgid "Create Date"
+msgstr ""
+
+#. module: project
+#: view:res.partner:0
+msgid "For changing to open state"
+msgstr ""
+
+#. module: project
+#: view:project.config.settings:0
+msgid "Apply"
+msgstr ""
+
+#. module: project
+#: model:ir.model,name:project.model_project_task_delegate
+msgid "Task Delegate"
+msgstr ""
+
+#. module: project
+#: help:project.task.delegate,new_task_description:0
+msgid "Reinclude the description of the task in the task of the user"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Project Settings"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+msgid "My tasks"
+msgstr ""
+
+#. module: project
+#: model:process.transition,name:project.process_transition_opendonetask0
+msgid "Open Done Task"
+msgstr ""
+
+#. module: project
+#: field:project.task.delegate,planned_hours_me:0
+msgid "Hours to Validate"
+msgstr ""
+
+#. module: project
+#: help:project.task,remaining_hours:0
+msgid ""
+"Total remaining time, can be re-estimated periodically by the assignee of "
+"the task."
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,month:0
+msgid "March"
+msgstr ""
+
+#. module: project
+#: view:board.board:0
+#: model:ir.actions.act_window,name:project.my_open_tasks_action
+#: view:project.task:0
+#: view:project.task.history.cumulative:0
+msgid "My Tasks"
+msgstr ""
+
+#. module: project
+#: constraint:project.task:0
+msgid "Error ! You cannot create recursive tasks."
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+msgid "Pending tasks"
+msgstr ""
+
+#. module: project
+#: view:project.task.reevaluate:0
+msgid "_Evaluate"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+#: field:report.project.task.user,opening_days:0
+msgid "Days to Open"
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,priority:0
+msgid "Very urgent"
+msgstr ""
+
+#. module: project
+#: help:project.task.delegate,project_id:0
+#: help:project.task.delegate,user_id:0
+msgid "User you want to delegate this task to"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Set as Template"
+msgstr ""
+
+#. module: project
+#: model:ir.model,name:project.model_project_task
+#: view:project.config.settings:0
+#: view:project.project:0
+#: view:project.task:0
+#: field:project.task.history,task_id:0
+#: field:project.task.history.cumulative,task_id:0
+#: field:project.task.work,task_id:0
+#: view:report.project.task.user:0
+msgid "Task"
+msgstr ""
+
+#. module: project
+#: help:project.config.settings,group_tasks_work_on_tasks:0
+msgid "Allows you to compute work on tasks."
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Administration"
+msgstr ""
+
+#. module: project
+#: field:project.config.settings,group_tasks_work_on_tasks:0
+msgid "Log work activities on tasks"
+msgstr ""
+
+#. module: project
+#: model:project.task.type,name:project.project_tt_analysis
+msgid "Analysis"
+msgstr ""
+
+#. module: project
+#: field:project.task,name:0
+#: field:report.project.task.user,name:0
+msgid "Task Summary"
+msgstr ""
+
+#. module: project
+#: field:project.task,active:0
+msgid "Not a Template Task"
+msgstr ""
+
+#. module: project
+#: field:project.task,planned_hours:0
+msgid "Initially Planned Hours"
+msgstr ""
+
+#. module: project
+#: model:process.transition,note:project.process_transition_delegate0
+msgid "Delegates tasks to the other user"
+msgstr ""
+
+#. module: project
+#: help:project.task,effective_hours:0
+msgid "Computed using the sum of the task work done."
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,help:project.open_view_project_all
+msgid ""
+"
\n"
+" Click to start a new project.\n"
+"
\n"
+" Projects are used to organize your activities; plan\n"
+" tasks, track issues, invoice timesheets. You can define\n"
+" internal projects (R&D, Improve Sales Process),\n"
+" private projects (My Todos) or customer ones.\n"
+"
\n"
+" You will be able collaborate with internal users on\n"
+" projects or invite customers to share your activities.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: project
+#: view:project.config.settings:0
+msgid "Planning"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: field:project.task,date_deadline:0
+#: field:report.project.task.user,date_deadline:0
+msgid "Deadline"
+msgstr ""
+
+#. module: project
+#: view:project.task.history.cumulative:0
+msgid "Ready"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "New Tasks"
+msgstr ""
+
+#. module: project
+#: field:project.config.settings,module_project_issue_sheet:0
+msgid "Invoice working time on issues"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+#: view:project.task:0
+#: field:project.task.history,end_date:0
+#: field:project.task.history.cumulative,end_date:0
+msgid "End Date"
+msgstr ""
+
+#. module: project
+#: model:project.task.type,name:project.project_tt_specification
+msgid "Specification"
+msgstr ""
+
+#. module: project
+#: model:process.transition,note:project.process_transition_draftopentask0
+msgid "From draft state, it will come into the open state."
+msgstr ""
+
+#. module: project
+#: view:project.task.history.cumulative:0
+msgid "Task's Analysis"
+msgstr ""
+
+#. module: project
+#: view:project.task.delegate:0
+#: field:project.task.delegate,new_task_description:0
+msgid "New Task Description"
+msgstr ""
+
+#. module: project
+#: field:report.project.task.user,delay_endings_days:0
+msgid "Overpassed Deadline"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+msgid "New tasks"
+msgstr ""
+
+#. module: project
+#: selection:project.task,priority:0
+#: selection:report.project.task.user,priority:0
+msgid "Medium"
+msgstr ""
+
+#. module: project
+#: field:project.project,total_hours:0
+msgid "Total Time"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+msgid "Creation Date"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Miscellaneous"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: field:project.task,stage_id:0
+#: field:project.task.history,type_id:0
+#: field:project.task.history.cumulative,type_id:0
+msgid "Stage"
+msgstr ""
+
+#. module: project
+#: model:process.transition,name:project.process_transition_draftopentask0
+msgid "Draft Open task"
+msgstr ""
+
+#. module: project
+#: field:project.project,alias_model:0
+msgid "Alias Model"
+msgstr ""
+
+#. module: project
+#: help:report.project.task.user,closing_days:0
+msgid "Number of Days to close the task"
+msgstr ""
+
+#. module: project
+#: view:board.board:0
+msgid "My Board"
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,name:project.open_task_type_form
+msgid "Stages"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+#: view:project.task:0
+msgid "Delete"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+msgid "In progress"
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,month:0
+msgid "September"
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,priority:0
+msgid "Urgent"
+msgstr ""
+
+#. module: project
+#: model:project.category,name:project.project_category_02
+msgid "Feature request"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Delegated tasks"
+msgstr ""
+
+#. module: project
+#: model:ir.model,name:project.model_project_task_work
+msgid "Project Task Work"
+msgstr ""
+
+#. module: project
+#: code:addons/project/wizard/project_task_delegate.py:81
+#, python-format
+msgid "CHECK: %s"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Close Project"
+msgstr ""
+
+#. module: project
+#: field:project.project,tasks:0
+msgid "Task Activities"
+msgstr ""
+
+#. module: project
+#: field:project.project,effective_hours:0
+#: field:project.task.work,hours:0
+msgid "Time Spent"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+#: view:project.task:0
+msgid "í"
+msgstr ""
+
+#. module: project
+#: field:account.analytic.account,company_uom_id:0
+msgid "unknown"
+msgstr ""
+
+#. module: project
+#: field:project.project,message_is_follower:0
+#: field:project.task,message_is_follower:0
+msgid "Is a Follower"
+msgstr ""
+
+#. module: project
+#: field:project.task,work_ids:0
+msgid "Work done"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+msgid "Extended Filters..."
+msgstr ""
+
+#. module: project
+#: model:ir.ui.menu,name:project.menu_tasks_config
+msgid "GTD"
+msgstr ""
+
+#. module: project
+#: help:project.task,state:0
+msgid ""
+"The status is set to 'Draft', when a case is created. "
+"If the case is in progress the status is set to 'Open'. "
+"When the case is over, the status is set to 'Done'. If "
+"the case needs to be reviewed then the status is set "
+"to 'Pending'."
+msgstr ""
+
+#. module: project
+#: model:ir.model,name:project.model_res_company
+msgid "Companies"
+msgstr ""
+
+#. module: project
+#: field:project.task.type,fold:0
+msgid "Folded by Default"
+msgstr ""
+
+#. module: project
+#: field:project.task.history,date:0
+#: field:project.task.history.cumulative,date:0
+#: field:project.task.work,date:0
+msgid "Date"
+msgstr ""
+
+#. module: project
+#: help:project.config.settings,module_project_issue:0
+msgid ""
+"Provides management of issues/bugs in projects.\n"
+" This installs the module project_issue."
+msgstr ""
+
+#. module: project
+#: help:project.task,kanban_state:0
+msgid ""
+"A task's kanban state indicates special situations affecting it:\n"
+" * Normal is the default situation\n"
+" * Blocked indicates something is preventing the progress of this task\n"
+" * Ready for next stage indicates the task is ready to be pulled to the next "
+"stage"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "10"
+msgstr ""
+
+#. module: project
+#: help:project.project,analytic_account_id:0
+msgid ""
+"Link this project to an analytic account if you need financial management on "
+"projects. It enables you to connect projects with budgets, planning, cost "
+"and revenue analysis, timesheets on projects, etc."
+msgstr ""
+
+#. module: project
+#: model:process.transition.action,name:project.process_transition_action_draftcanceltask0
+#: model:process.transition.action,name:project.process_transition_action_opencanceltask0
+#: view:project.config.settings:0
+#: view:project.task.delegate:0
+#: view:project.task.reevaluate:0
+msgid "Cancel"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Other Info"
+msgstr ""
+
+#. module: project
+#: view:project.task.delegate:0
+msgid "_Delegate"
+msgstr ""
+
+#. module: project
+#: selection:project.task,priority:0
+#: selection:report.project.task.user,priority:0
+msgid "Very Low"
+msgstr ""
+
+#. module: project
+#: help:project.project,effective_hours:0
+msgid ""
+"Sum of spent hours of all tasks related to this project and its child "
+"projects."
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid ""
+"Follow this project to automatically track the events associated to tasks "
+"and issues of this project."
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Users"
+msgstr ""
+
+#. module: project
+#: model:mail.message.subtype,name:project.mt_task_stage
+msgid "Stage Changed"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+#: model:res.groups,name:project.group_project_manager
+msgid "Manager"
+msgstr ""
+
+#. module: project
+#: selection:project.task,priority:0
+msgid "Important"
+msgstr ""
+
+#. module: project
+#: field:project.category,name:0
+msgid "Name"
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,month:0
+msgid "November"
+msgstr ""
+
+#. module: project
+#: view:project.task.reevaluate:0
+msgid "Reevaluate Task"
+msgstr ""
+
+#. module: project
+#: model:ir.model,name:project.model_project_task_type
+#: view:project.task.type:0
+msgid "Task Stage"
+msgstr ""
+
+#. module: project
+#: view:project.task.type:0
+msgid "Common"
+msgstr ""
+
+#. module: project
+#: help:project.project,message_ids:0
+#: help:project.task,message_ids:0
+msgid "Messages and communication history"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid ""
+"To invoice or setup invoicing and renewal options, go to the related "
+"contract:"
+msgstr ""
+
+#. module: project
+#: field:project.task.delegate,state:0
+msgid "Validation State"
+msgstr ""
+
+#. module: project
+#: field:project.task.work,name:0
+msgid "Work summary"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+#: view:project.task:0
+#: view:report.project.task.user:0
+msgid "Group By..."
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+#: selection:project.project,state:0
+msgid "Template"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Re-open project"
+msgstr ""
+
+#. module: project
+#: help:project.project,priority:0
+msgid "Gives the sequence order when displaying the list of projects"
+msgstr ""
+
+#. module: project
+#: constraint:project.project:0
+msgid "Error! project start-date must be lower then project end-date."
+msgstr ""
+
+#. module: project
+#: field:project.project,members:0
+msgid "Project Members"
+msgstr ""
+
+#. module: project
+#: field:project.task,child_ids:0
+msgid "Delegated Tasks"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+#: field:project.project,message_unread:0
+#: view:project.task:0
+#: field:project.task,message_unread:0
+msgid "Unread Messages"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: field:project.task,parent_ids:0
+msgid "Parent Tasks"
+msgstr ""
+
+#. module: project
+#: model:process.node,name:project.process_node_opentask0
+msgid "Open task"
+msgstr ""
+
+#. module: project
+#: view:project.task.type:0
+msgid "Stages common to all projects"
+msgstr ""
+
+#. module: project
+#: model:process.node,name:project.process_node_drafttask0
+msgid "Draft task"
+msgstr ""
+
+#. module: project
+#: field:project.task,notes:0
+msgid "Notes"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: view:project.task.history.cumulative:0
+msgid "Pending Tasks"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Show only tasks having a deadline"
+msgstr ""
+
+#. module: project
+#: model:project.category,name:project.project_category_04
+msgid "Usability"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+#: field:report.project.task.user,hours_delay:0
+msgid "Avg. Plan.-Eff."
+msgstr ""
+
+#. module: project
+#: field:project.task.work,user_id:0
+msgid "Done by"
+msgstr ""
+
+#. module: project
+#: code:addons/project/project.py:181
+#, python-format
+msgid "Invalid Action!"
+msgstr ""
+
+#. module: project
+#: help:project.task.type,state:0
+msgid ""
+"The status of your document is automatically changed regarding the selected "
+"stage. For example, if a stage is related to the status 'Close', when your "
+"document reaches this stage, it is automatically closed."
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Extra Info"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Edit..."
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+#: field:report.project.task.user,nbr:0
+msgid "# of tasks"
+msgstr ""
+
+#. module: project
+#: field:project.project,doc_count:0
+msgid "Number of documents attached"
+msgstr ""
+
+#. module: project
+#: field:project.task,priority:0
+#: field:report.project.task.user,priority:0
+msgid "Priority"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Open Projects"
+msgstr ""
+
+#. module: project
+#: help:project.project,alias_id:0
+msgid ""
+"Internal email associated with this project. Incoming emails are "
+"automatically synchronizedwith Tasks (or optionally Issues if the Issue "
+"Tracker module is installed)."
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,help:project.open_task_type_form
+msgid ""
+"
\n"
+" Click to add a stage in the task pipeline.\n"
+"
\n"
+" Define the steps that will be used in the project from the\n"
+" creation of the task, up to the closing of the task or "
+"issue.\n"
+" You will use these stages in order to track the progress in\n"
+" solving a task or an issue.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: project
+#: help:project.task,total_hours:0
+msgid "Computed as: Time Spent + Remaining Time."
+msgstr ""
+
+#. module: project
+#: code:addons/project/project.py:368
+#: code:addons/project/project.py:389
+#: code:addons/project/project.py:721
+#, python-format
+msgid "%s (copy)"
+msgstr ""
+
+#. module: project
+#: model:mail.message.subtype,name:project.mt_project_task_stage
+msgid "Task Stage Changed"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: field:project.task.history,remaining_hours:0
+#: field:project.task.history.cumulative,remaining_hours:0
+msgid "Remaining Time"
+msgstr ""
+
+#. module: project
+#: field:project.task.delegate,name:0
+msgid "Delegated Title"
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,month:0
+msgid "July"
+msgstr ""
+
+#. module: project
+#: model:ir.model,name:project.model_project_task_reevaluate
+msgid "project.task.reevaluate"
+msgstr ""
+
+#. module: project
+#: field:project.task,delay_hours:0
+msgid "Delay Hours"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Team"
+msgstr ""
+
+#. module: project
+#: help:project.config.settings,time_unit:0
+msgid "This will set the unit of measure used in projects and tasks."
+msgstr ""
+
+#. module: project
+#: selection:project.task,priority:0
+msgid "Very important"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+#: field:report.project.task.user,month:0
+msgid "Month"
+msgstr ""
+
+#. module: project
+#: model:project.task.type,name:project.project_tt_design
+msgid "Design"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Start Date"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: selection:project.task,kanban_state:0
+#: selection:project.task.history,kanban_state:0
+#: view:project.task.history.cumulative:0
+#: selection:project.task.history.cumulative,kanban_state:0
+msgid "Blocked"
+msgstr ""
+
+#. module: project
+#: help:project.task,progress:0
+msgid ""
+"If the task has a progress of 99.99% you should close the task if it's "
+"finished or reevaluate the time"
+msgstr ""
+
+#. module: project
+#: field:project.task,user_email:0
+msgid "User Email"
+msgstr ""
+
+#. module: project
+#: help:project.task.delegate,prefix:0
+msgid "Title for your validation task"
+msgstr ""
+
+#. module: project
+#: field:project.config.settings,time_unit:0
+msgid "Working time unit"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Projects in which I am a member."
+msgstr ""
+
+#. module: project
+#: selection:project.task,priority:0
+#: selection:report.project.task.user,priority:0
+msgid "Low"
+msgstr ""
+
+#. module: project
+#: selection:project.project,state:0
+msgid "Closed"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+#: selection:project.project,state:0
+#: view:project.task:0
+#: selection:project.task,state:0
+#: selection:project.task.delegate,state:0
+#: selection:project.task.history,state:0
+#: selection:project.task.history.cumulative,state:0
+#: selection:project.task.type,state:0
+#: view:report.project.task.user:0
+#: selection:report.project.task.user,state:0
+msgid "Pending"
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,name:project.project_category_action
+#: model:ir.ui.menu,name:project.menu_project_category_act
+#: view:project.category:0
+#: field:project.task,categ_ids:0
+msgid "Tags"
+msgstr ""
+
+#. module: project
+#: model:ir.model,name:project.model_project_task_history
+#: model:ir.model,name:project.model_project_task_history_cumulative
+msgid "History of Tasks"
+msgstr ""
+
+#. module: project
+#: help:project.task.delegate,state:0
+msgid ""
+"New state of your own task. Pending will be reopened automatically when the "
+"delegated task is closed"
+msgstr ""
+
+#. module: project
+#: help:project.config.settings,group_manage_delegation_task:0
+msgid "Allows you to delegate tasks to other users."
+msgstr ""
+
+#. module: project
+#: field:project.project,active:0
+msgid "Active"
+msgstr ""
+
+#. module: project
+#: model:ir.model,name:project.model_project_category
+msgid "Category of project's task, issue, ..."
+msgstr ""
+
+#. module: project
+#: help:project.project,resource_calendar_id:0
+msgid "Timetable working hours to adjust the gantt diagram report"
+msgstr ""
+
+#. module: project
+#: help:project.task,delay_hours:0
+msgid ""
+"Computed as difference between planned hours by the project manager and the "
+"total hours of the task."
+msgstr ""
+
+#. module: project
+#: view:project.config.settings:0
+msgid "Helpdesk & Support"
+msgstr ""
+
+#. module: project
+#: help:report.project.task.user,opening_days:0
+msgid "Number of Days to Open the task"
+msgstr ""
+
+#. module: project
+#: field:project.task,delegated_user_id:0
+msgid "Delegated To"
+msgstr ""
+
+#. module: project
+#: help:project.task,planned_hours:0
+msgid ""
+"Estimated time to do the task, usually set by the project manager when the "
+"task is in draft state."
+msgstr ""
+
+#. module: project
+#: code:addons/project/project.py:230
+#, python-format
+msgid "Attachments"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: selection:project.task,state:0
+#: selection:project.task.delegate,state:0
+#: selection:project.task.history,state:0
+#: selection:project.task.history.cumulative,state:0
+#: model:project.task.type,name:project.project_tt_deployment
+#: selection:project.task.type,state:0
+#: view:report.project.task.user:0
+#: selection:report.project.task.user,state:0
+#: view:res.partner:0
+msgid "Done"
+msgstr ""
+
+#. module: project
+#: code:addons/project/project.py:182
+#, python-format
+msgid ""
+"You cannot delete a project containing tasks. You can either delete all the "
+"project's tasks and then delete the project or simply deactivate the project."
+msgstr ""
+
+#. module: project
+#: model:process.transition.action,name:project.process_transition_action_draftopentask0
+#: view:project.project:0
+msgid "Open"
+msgstr ""
+
+#. module: project
+#: field:project.project,privacy_visibility:0
+msgid "Privacy / Visibility"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: field:project.task,remaining_hours:0
+#: field:project.task.reevaluate,remaining_hours:0
+#: view:report.project.task.user:0
+#: field:report.project.task.user,remaining_hours:0
+msgid "Remaining Hours"
+msgstr ""
+
+#. module: project
+#: model:mail.message.subtype,description:project.mt_task_stage
+msgid "Stage changed"
+msgstr ""
+
+#. module: project
+#: constraint:project.task:0
+msgid "Error ! Task end-date must be greater then task start-date"
+msgstr ""
+
+#. module: project
+#: field:project.task.history,user_id:0
+#: field:project.task.history.cumulative,user_id:0
+msgid "Responsible"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Search Project"
+msgstr ""
+
+#. module: project
+#: view:project.task.delegate:0
+msgid "Delegated Task"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+#: field:report.project.task.user,total_hours:0
+msgid "Total Hours"
+msgstr ""
+
+#. module: project
+#: model:ir.model,name:project.model_project_config_settings
+msgid "project.config.settings"
+msgstr ""
+
+#. module: project
+#: model:project.task.type,name:project.project_tt_development
+msgid "Development"
+msgstr ""
+
+#. module: project
+#: help:project.task,active:0
+msgid ""
+"This field is computed automatically and have the same behavior than the "
+"boolean 'active' field: if the task is linked to a template or unactivated "
+"project, it will be hidden unless specifically asked."
+msgstr ""
+
+#. module: project
+#: model:res.request.link,name:project.req_link_task
+msgid "Project task"
+msgstr ""
+
+#. module: project
+#: field:project.task,effective_hours:0
+msgid "Hours Spent"
+msgstr ""
+
+#. module: project
+#: help:project.config.settings,module_pad:0
+msgid ""
+"Lets the company customize which Pad installation should be used to link to "
+"new pads\n"
+" (by default, http://ietherpad.com/).\n"
+" This installs the module pad."
+msgstr ""
+
+#. module: project
+#: field:project.task,id:0
+msgid "ID"
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,name:project.action_view_task_overpassed_draft
+msgid "Overpassed Tasks"
+msgstr ""
+
+#. module: project
+#: code:addons/project/project.py:944
+#, python-format
+msgid ""
+"Child task still open.\n"
+"Please cancel or complete child task first."
+msgstr ""
+
+#. module: project
+#: field:project.task.delegate,user_id:0
+msgid "Assign To"
+msgstr ""
+
+#. module: project
+#: model:res.groups,name:project.group_time_work_estimation_tasks
+msgid "Time Estimation on Tasks"
+msgstr ""
+
+#. module: project
+#: field:project.task,total_hours:0
+msgid "Total"
+msgstr ""
+
+#. module: project
+#: model:process.node,note:project.process_node_taskbydelegate0
+msgid "Delegate your task to the other user"
+msgstr ""
+
+#. module: project
+#: model:mail.message.subtype,description:project.mt_task_started
+msgid "Task started"
+msgstr ""
+
+#. module: project
+#: help:project.task.reevaluate,remaining_hours:0
+msgid "Put here the remaining hours required to close the task."
+msgstr ""
+
+#. module: project
+#: help:project.task.type,fold:0
+msgid ""
+"This stage is not visible, for example in status bar or kanban view, when "
+"there are no records in that stage to display."
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Deadlines"
+msgstr ""
+
+#. module: project
+#: code:addons/project/wizard/project_task_delegate.py:69
+#: code:addons/project/wizard/project_task_delegate.py:70
+#: code:addons/project/wizard/project_task_delegate.py:77
+#: code:addons/project/wizard/project_task_delegate.py:78
+#, python-format
+msgid "CHECK: "
+msgstr ""
+
+#. module: project
+#: code:addons/project/project.py:444
+#, python-format
+msgid "You must assign members on the project '%s' !"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Pending Projects"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Remaining"
+msgstr ""
+
+#. module: project
+#: field:project.task,progress:0
+msgid "Progress (%)"
+msgstr ""
+
+#. module: project
+#: field:project.task,company_id:0
+#: field:project.task.work,company_id:0
+#: view:report.project.task.user:0
+#: field:report.project.task.user,company_id:0
+msgid "Company"
+msgstr ""
+
+#. module: project
+#: help:project.config.settings,module_project_timesheet:0
+msgid ""
+"This allows you to transfer the entries under tasks defined for Project "
+"Management to\n"
+" the timesheet line entries for particular date and user, "
+"with the effect of creating,\n"
+" editing and deleting either ways.\n"
+" This installs the module project_timesheet."
+msgstr ""
+
+#. module: project
+#: field:project.config.settings,module_project_issue:0
+msgid "Track issues and bugs"
+msgstr ""
+
+#. module: project
+#: field:project.config.settings,module_project_mrp:0
+msgid "Generate tasks from sale orders"
+msgstr ""
+
+#. module: project
+#: model:ir.ui.menu,name:project.menu_task_types_view
+msgid "Task Stages"
+msgstr ""
+
+#. module: project
+#: model:process.node,note:project.process_node_drafttask0
+msgid "Define the Requirements and Set Planned Hours."
+msgstr ""
+
+#. module: project
+#: field:project.project,message_ids:0
+#: field:project.task,message_ids:0
+msgid "Messages"
+msgstr ""
+
+#. module: project
+#: field:project.project,color:0
+#: field:project.task,color:0
+msgid "Color Index"
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,name:project.open_board_project
+#: model:ir.model,name:project.model_project_project
+#: model:ir.ui.menu,name:project.menu_project_dashboard
+#: model:ir.ui.menu,name:project.menu_project_management
+#: view:project.project:0
+#: view:project.task:0
+#: field:project.task,project_id:0
+#: field:project.task.delegate,project_id:0
+#: field:project.task.history.cumulative,project_id:0
+#: view:report.project.task.user:0
+#: field:report.project.task.user,project_id:0
+#: model:res.request.link,name:project.req_link_project
+msgid "Project"
+msgstr ""
+
+#. module: project
+#: selection:project.project,state:0
+#: selection:project.task,state:0
+#: selection:project.task.history,state:0
+#: selection:project.task.history.cumulative,state:0
+#: model:project.task.type,name:project.project_tt_cancel
+#: selection:project.task.type,state:0
+#: selection:report.project.task.user,state:0
+msgid "Cancelled"
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,help:project.act_project_project_2_project_task_all
+#: model:ir.actions.act_window,help:project.action_view_task
+msgid ""
+"
\n"
+" Click to create a new task.\n"
+"
\n"
+" OpenERP's project management allows you to manage the "
+"pipeline\n"
+" of tasks in order to get things done efficiently. You can\n"
+" track progress, discuss on tasks, attach documents, etc.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: project
+#: field:project.task,date_end:0
+#: field:report.project.task.user,date_end:0
+msgid "Ending Date"
+msgstr ""
+
+#. module: project
+#: field:project.task.type,state:0
+msgid "Related Status"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Documents"
+msgstr ""
+
+#. module: project
+#: model:mail.message.subtype,description:project.mt_task_new
+msgid "Task created"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+#: field:report.project.task.user,no_of_days:0
+msgid "# of Days"
+msgstr ""
+
+#. module: project
+#: field:project.project,message_follower_ids:0
+#: field:project.task,message_follower_ids:0
+msgid "Followers"
+msgstr ""
+
+#. module: project
+#: selection:project.project,state:0
+#: view:project.task:0
+#: selection:project.task,state:0
+#: selection:project.task.history,state:0
+#: selection:project.task.history.cumulative,state:0
+#: selection:project.task.type,state:0
+#: view:report.project.task.user:0
+msgid "New"
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,name:project.action_view_task_history_cumulative
+#: model:ir.ui.menu,name:project.menu_action_view_task_history_cumulative
+msgid "Cumulative Flow"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+#: field:report.project.task.user,hours_effective:0
+msgid "Effective Hours"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+msgid "OverPass delay"
+msgstr ""
+
+#. module: project
+#: view:project.task.delegate:0
+msgid "Validation Task"
+msgstr ""
+
+#. module: project
+#: field:project.config.settings,module_project_long_term:0
+msgid "Manage resources planning on gantt view"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: view:project.task.history.cumulative:0
+msgid "Unassigned Tasks"
+msgstr ""
+
+#. module: project
+#: help:project.project,planned_hours:0
+msgid ""
+"Sum of planned hours of all tasks related to this project and its child "
+"projects."
+msgstr ""
+
+#. module: project
+#: view:res.partner:0
+msgid "For changing to done state"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+msgid "My Task"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: view:project.task.history.cumulative:0
+#: view:report.project.task.user:0
+msgid "My Projects"
+msgstr ""
+
+#. module: project
+#: help:project.task,sequence:0
+msgid "Gives the sequence order when displaying a list of tasks."
+msgstr ""
+
+#. module: project
+#: field:project.task,date_start:0
+#: field:report.project.task.user,date_start:0
+msgid "Starting Date"
+msgstr ""
+
+#. module: project
+#: code:addons/project/project.py:410
+#: model:ir.actions.act_window,name:project.open_view_project_all
+#: model:ir.ui.menu,name:project.menu_projects
+#: view:project.project:0
+#: field:project.task.type,project_ids:0
+#: view:res.company:0
+#, python-format
+msgid "Projects"
+msgstr ""
+
+#. module: project
+#: model:res.groups,name:project.group_tasks_work_on_tasks
+msgid "Task's Work on Tasks"
+msgstr ""
+
+#. module: project
+#: help:project.task.delegate,name:0
+msgid "New title of the task delegated to the user"
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,name:project.action_project_task_user_tree
+#: model:ir.ui.menu,name:project.menu_project_task_user_tree
+#: view:report.project.task.user:0
+msgid "Tasks Analysis"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: view:project.task.history.cumulative:0
+msgid "Project Tasks"
+msgstr ""
+
+#. module: project
+#: field:account.analytic.account,use_tasks:0
+#: model:ir.actions.act_window,name:project.act_project_project_2_project_task_all
+#: model:ir.actions.act_window,name:project.action_view_task
+#: model:ir.ui.menu,name:project.menu_action_view_task
+#: model:process.process,name:project.process_process_tasksprocess0
+#: view:project.project:0
+#: view:project.task:0
+#: view:res.partner:0
+#: field:res.partner,task_ids:0
+msgid "Tasks"
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,month:0
+msgid "December"
+msgstr ""
+
+#. module: project
+#: view:project.config.settings:0
+#: view:project.task.delegate:0
+#: view:project.task.reevaluate:0
+msgid "or"
+msgstr ""
+
+#. module: project
+#: help:project.config.settings,module_project_mrp:0
+msgid ""
+"This feature automatically creates project tasks from service products in "
+"sale orders.\n"
+" More precisely, tasks are created for procurement lines with "
+"product of type 'Service',\n"
+" procurement method 'Make to Order', and supply method "
+"'Manufacture'.\n"
+" This installs the module project_mrp."
+msgstr ""
+
+#. module: project
+#: help:project.task.delegate,planned_hours:0
+msgid "Estimated time to close this task by the delegated user"
+msgstr ""
+
+#. module: project
+#: model:project.category,name:project.project_category_03
+msgid "Experiment"
+msgstr ""
+
+#. module: project
+#: model:process.transition.action,name:project.process_transition_action_opendrafttask0
+#: selection:report.project.task.user,state:0
+msgid "Draft"
+msgstr ""
+
+#. module: project
+#: field:project.task,kanban_state:0
+#: field:project.task.history,kanban_state:0
+#: field:project.task.history.cumulative,kanban_state:0
+msgid "Kanban State"
+msgstr ""
+
+#. module: project
+#: field:project.config.settings,module_project_timesheet:0
+msgid "Record timesheet lines per tasks"
+msgstr ""
+
+#. module: project
+#: model:ir.model,name:project.model_report_project_task_user
+msgid "Tasks by user and project"
+msgstr ""
+
+#. module: project
+#: field:res.company,project_time_mode_id:0
+msgid "Project Time Unit"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: selection:project.task,kanban_state:0
+#: selection:project.task.history,kanban_state:0
+#: selection:project.task.history.cumulative,kanban_state:0
+msgid "Normal"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+#: field:report.project.task.user,closing_days:0
+msgid "Days to Close"
+msgstr ""
+
+#. module: project
+#: model:res.groups,name:project.group_project_user
+msgid "User"
+msgstr ""
+
+#. module: project
+#: help:project.project,alias_model:0
+msgid ""
+"The kind of document created when an email is received on this project's "
+"email alias"
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,name:project.action_config_settings
+#: view:project.config.settings:0
+msgid "Configure Project"
+msgstr ""
+
+#. module: project
+#: view:project.task.history.cumulative:0
+msgid "Tasks's Cumulative Flow"
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,month:0
+msgid "January"
+msgstr ""
+
+#. module: project
+#: field:project.task.delegate,prefix:0
+msgid "Your Task Title"
+msgstr ""
+
+#. module: project
+#: view:project.task.reevaluate:0
+msgid "Reevaluation Task"
+msgstr ""
+
+#. module: project
+#: code:addons/project/project.py:1332
+#, python-format
+msgid "Please delete the project linked with this account first."
+msgstr ""
+
+#. module: project
+#: model:mail.message.subtype,name:project.mt_project_task_new
+#: model:mail.message.subtype,name:project.mt_task_new
+msgid "Task Created"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+msgid "Non Assigned Tasks to users"
+msgstr ""
+
+#. module: project
+#: view:project.project:0
+msgid "Projects in which I am a manager"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: selection:project.task,kanban_state:0
+#: selection:project.task.history,kanban_state:0
+#: selection:project.task.history.cumulative,kanban_state:0
+msgid "Ready for next stage"
+msgstr ""
+
+#. module: project
+#: field:project.task.type,case_default:0
+msgid "Default for New Projects"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: field:project.task,description:0
+#: field:project.task.type,description:0
+msgid "Description"
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,month:0
+msgid "May"
+msgstr ""
+
+#. module: project
+#: help:project.task.type,case_default:0
+msgid ""
+"If you check this field, this stage will be proposed by default on each new "
+"project. It will not assign this stage to existing projects."
+msgstr ""
+
+#. module: project
+#: field:project.task,partner_id:0
+msgid "Customer"
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,month:0
+msgid "February"
+msgstr ""
+
+#. module: project
+#: help:project.config.settings,module_project_long_term:0
+msgid ""
+"A long term project management module that tracks planning, scheduling, and "
+"resource allocation.\n"
+" This installs the module project_long_term."
+msgstr ""
+
+#. module: project
+#: model:mail.message.subtype,description:project.mt_task_closed
+msgid "Task closed"
+msgstr ""
+
+#. module: project
+#: selection:report.project.task.user,month:0
+msgid "April"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+msgid "Spent Hours"
+msgstr ""
+
+#. module: project
+#: help:project.project,sequence:0
+msgid "Gives the sequence order when displaying a list of Projects."
+msgstr ""
+
+#. module: project
+#: model:ir.actions.act_window,name:project.act_res_users_2_project_task_opened
+msgid "Assigned Tasks"
+msgstr ""
+
+#. module: project
+#: help:project.config.settings,module_project_issue_sheet:0
+msgid ""
+"Provides timesheet support for the issues/bugs management in project.\n"
+" This installs the module project_issue_sheet."
+msgstr ""
+
+#. module: project
+#: selection:project.project,privacy_visibility:0
+msgid "Followers Only"
+msgstr ""
+
+#. module: project
+#: view:board.board:0
+#: field:project.project,task_count:0
+msgid "Open Tasks"
+msgstr ""
+
+#. module: project
+#: field:project.project,priority:0
+#: field:project.project,sequence:0
+#: field:project.task,sequence:0
+#: field:project.task.type,sequence:0
+msgid "Sequence"
+msgstr ""
+
+#. module: project
+#: view:project.task:0
+#: view:project.task.work:0
+msgid "Task Work"
+msgstr ""
+
+#. module: project
+#: help:project.task.delegate,planned_hours_me:0
+msgid ""
+"Estimated time for you to validate the work done by the user to whom you "
+"delegate this task"
+msgstr ""
+
+#. module: project
+#: view:report.project.task.user:0
+#: field:report.project.task.user,year:0
+msgid "Year"
+msgstr ""
+
+#. module: project
+#: field:project.project,type_ids:0
+#: view:project.task.type:0
+msgid "Tasks Stages"
+msgstr ""
diff --git a/addons/project/project.py b/addons/project/project.py
index ea9c4492fa4..e42cd8d7247 100644
--- a/addons/project/project.py
+++ b/addons/project/project.py
@@ -479,7 +479,7 @@ def Project():
""" % (
project.id,
project.date_start or time.strftime('%Y-%m-%d'), working_days,
- '|'.join(['User_'+str(x) for x in puids])
+ '|'.join(['User_'+str(x) for x in puids]) or 'None'
)
vacation = calendar_id and tuple(resource_pool.compute_vacation(cr, uid, calendar_id, context=context)) or False
if vacation:
@@ -789,7 +789,7 @@ class task(osv.osv):
}),
'progress': fields.function(_hours_get, string='Working Time Progress (%)', multi='hours', group_operator="avg", help="If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
store = {
- 'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours','state'], 10),
+ 'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours', 'state', 'stage_id'], 10),
'project.task.work': (_get_task, ['hours'], 10),
}),
'delay_hours': fields.function(_hours_get, string='Delay Hours', multi='hours', help="Computed as difference between planned hours by the project manager and the total hours of the task.",
diff --git a/addons/project/static/src/js/project.js b/addons/project/static/src/js/project.js
index ce8be146e54..a4e899a8714 100644
--- a/addons/project/static/src/js/project.js
+++ b/addons/project/static/src/js/project.js
@@ -32,12 +32,6 @@ openerp.project = function(openerp) {
if (self.dataset.model === 'project.project') {
self.project_display_members_names();
}
- },
- on_record_moved: function(record, old_group, old_index, new_group, new_index){
- var self = this;
- this._super.apply(this, arguments);
- if(new_group.state.folded)
- new_group.do_action_toggle_fold();
}
});
diff --git a/addons/project_issue/i18n/he.po b/addons/project_issue/i18n/he.po
new file mode 100644
index 00000000000..a371ffffa71
--- /dev/null
+++ b/addons/project_issue/i18n/he.po
@@ -0,0 +1,984 @@
+# Hebrew translation for openobject-addons
+# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:04+0000\n"
+"PO-Revision-Date: 2013-12-26 23:15+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Hebrew \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2013-12-27 05:22+0000\n"
+"X-Generator: Launchpad (build 16877)\n"
+
+#. module: project_issue
+#: model:project.category,name:project_issue.project_issue_category_03
+msgid "Deadly bug"
+msgstr ""
+
+#. module: project_issue
+#: help:project.config.settings,fetchmail_issue:0
+msgid ""
+"Allows you to configure your incoming mail server, and create issues from "
+"incoming emails."
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue.report,delay_open:0
+msgid "Avg. Delay to Open"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: view:project.issue.report:0
+msgid "Group By..."
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,working_hours_open:0
+msgid "Working Hours to Open the Issue"
+msgstr ""
+
+#. module: project_issue
+#: model:mail.message.subtype,description:project_issue.mt_issue_started
+msgid "Issue started"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,date_open:0
+msgid "Opened"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue.report,opening_date:0
+msgid "Date of Opening"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,month:0
+msgid "March"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,progress:0
+msgid "Progress (%)"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: field:project.issue,message_unread:0
+msgid "Unread Messages"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,company_id:0
+#: view:project.issue.report:0
+#: field:project.issue.report,company_id:0
+msgid "Company"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,email_cc:0
+msgid "Watchers Emails"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,kanban_state:0
+msgid ""
+"A Issue's kanban state indicates special situations affecting it:\n"
+" * Normal is the default situation\n"
+" * Blocked indicates something is preventing the progress of this issue\n"
+" * Ready for next stage indicates the issue is ready to be pulled to the "
+"next stage"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,message_unread:0
+msgid "If checked new messages require your attention."
+msgstr ""
+
+#. module: project_issue
+#: help:account.analytic.account,use_issues:0
+msgid "Check this field if this project manages issues"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,day_open:0
+msgid "Days to Open"
+msgstr ""
+
+#. module: project_issue
+#: code:addons/project_issue/project_issue.py:479
+#, python-format
+msgid ""
+"You cannot escalate this issue.\n"
+"The relevant Project has not configured the Escalation Project!"
+msgstr ""
+
+#. module: project_issue
+#: constraint:project.project:0
+msgid "Error! You cannot assign escalation to the same project!"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue,priority:0
+#: selection:project.issue.report,priority:0
+msgid "Highest"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,inactivity_days:0
+msgid "Difference in days between last action and current date"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue.report:0
+#: field:project.issue.report,day:0
+msgid "Day"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,days_since_creation:0
+msgid "Days since creation date"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,task_id:0
+#: view:project.issue.report:0
+#: field:project.issue.report,task_id:0
+msgid "Task"
+msgstr ""
+
+#. module: project_issue
+#: model:mail.message.subtype,name:project_issue.mt_project_issue_stage
+msgid "Issue Stage Changed"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,message_ids:0
+msgid "Messages"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,inactivity_days:0
+msgid "Days since last action"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.model,name:project_issue.model_project_project
+#: view:project.issue:0
+#: field:project.issue,project_id:0
+#: view:project.issue.report:0
+#: field:project.issue.report,project_id:0
+msgid "Project"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.actions.act_window,help:project_issue.project_issue_categ_act0
+msgid ""
+"
\n"
+" Click to report a new issue.\n"
+"
\n"
+" The OpenERP issues tacker allows you to efficiantly manage "
+"things\n"
+" like internal requests, software development bugs, customer\n"
+" complaints, project troubles, material breakdowns, etc.\n"
+"