Project Planning module updated

This commit is contained in:
projectsodoo 2020-12-22 23:04:17 +05:30
parent a399edd6cc
commit 20ebf1b154
244 changed files with 93199 additions and 0 deletions

7
planning/__init__.py Normal file
View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
from . import controllers
from . import wizard
from . import report

36
planning/__manifest__.py Normal file
View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': "Planning",
'summary': """Manage your employees' schedule""",
'description': """
Schedule your teams and employees with shift.
""",
'category': 'Human Resources/Planning',
'version': '1.0',
'depends': ['hr', 'web_gantt'],
'data': [
'security/planning_security.xml',
'security/ir.model.access.csv',
'wizard/planning_send_views.xml',
'views/assets.xml',
'views/hr_views.xml',
'report/planning_report_views.xml',
'views/planning_template_views.xml',
'views/planning_views.xml',
'views/res_config_settings_views.xml',
'views/planning_templates.xml',
'data/planning_cron.xml',
'data/mail_data.xml',
],
'demo': [
'data/planning_demo.xml',
],
'application': True,
'license': 'OEEL-1',
'qweb': [
'static/src/xml/planning_gantt.xml',
'static/src/xml/field_colorpicker.xml',
]
}

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import main

View File

@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licens
from odoo import http, fields, _
from odoo.http import request
from odoo.osv import expression
import pytz
from werkzeug.utils import redirect
import babel
from werkzeug.exceptions import Forbidden
from odoo import tools
class ShiftController(http.Controller):
@http.route(['/planning/<string:planning_token>/<string:employee_token>'], type='http', auth="public", website=True)
def planning(self, planning_token, employee_token, message=False, **kwargs):
""" Displays an employee's calendar and the current list of open shifts """
employee_sudo = request.env['hr.employee'].sudo().search([('employee_token', '=', employee_token)], limit=1)
if not employee_sudo:
return request.not_found()
planning_sudo = request.env['planning.planning'].sudo().search([('access_token', '=', planning_token)], limit=1)
if not planning_sudo:
return request.not_found()
employee_tz = pytz.timezone(employee_sudo.tz or 'UTC')
employee_fullcalendar_data = []
open_slots = []
planning_slots = []
# domain of slots to display
domain = planning_sudo._get_domain_slots()[planning_sudo.id]
if planning_sudo.include_unassigned:
domain = expression.AND([domain, ['|', ('employee_id', '=', employee_sudo.id), ('employee_id', '=', False)]])
else:
domain = expression.AND([domain, [('employee_id', '=', employee_sudo.id)]])
planning_slots = request.env['planning.slot'].sudo().search(domain)
# filter and format slots
for slot in planning_slots:
if slot.employee_id:
employee_fullcalendar_data.append({
'title': '%s%s' % (slot.role_id.name, u' \U0001F4AC' if slot.name else ''),
'start': str(pytz.utc.localize(slot.start_datetime).astimezone(employee_tz).replace(tzinfo=None)),
'end': str(pytz.utc.localize(slot.end_datetime).astimezone(employee_tz).replace(tzinfo=None)),
'color': self._format_planning_shifts(slot.role_id.color),
'alloc_hours': slot.allocated_hours,
'slot_id': slot.id,
'note': slot.name,
'allow_self_unassign': slot.allow_self_unassign
})
else:
open_slots.append(slot)
return request.render('planning.period_report_template', {
'employee_slots_fullcalendar_data': employee_fullcalendar_data,
'open_slots_ids': open_slots,
'planning_slots_ids': planning_slots,
'planning_planning_id': planning_sudo,
'employee': employee_sudo,
'format_datetime': lambda dt, dt_format: tools.format_datetime(request.env, dt, dt_format=dt_format),
'message_slug': message,
})
@http.route('/planning/<string:token_planning>/<string:token_employee>/assign/<int:slot_id>', type="http", auth="public", methods=['post'], website=True)
def planning_self_assign(self, token_planning, token_employee, slot_id, **kwargs):
slot_sudo = request.env['planning.slot'].sudo().browse(slot_id)
if not slot_sudo.exists():
return request.not_found()
if slot_sudo.employee_id:
raise Forbidden(_('You can not assign yourself to this shift.'))
employee_sudo = request.env['hr.employee'].sudo().search([('employee_token', '=', token_employee)], limit=1)
if not employee_sudo:
return request.not_found()
planning_sudo = request.env['planning.planning'].sudo().search([('access_token', '=', token_planning)], limit=1)
if not planning_sudo or slot_sudo.id not in planning_sudo.slot_ids._ids:
return request.not_found()
slot_sudo.write({'employee_id': employee_sudo.id})
return redirect('/planning/%s/%s?message=%s' % (token_planning, token_employee, 'assign'))
@http.route('/planning/<string:token_planning>/<string:token_employee>/unassign/<int:shift_id>', type="http", auth="public", methods=['post'], website=True)
def planning_self_unassign(self, token_planning, token_employee, shift_id, **kwargs):
slot_sudo = request.env['planning.slot'].sudo().search([('id', '=', shift_id)], limit=1)
if not slot_sudo or not slot_sudo.allow_self_unassign:
return request.not_found()
employee_sudo = request.env['hr.employee'].sudo().search([('employee_token', '=', token_employee)], limit=1)
if not employee_sudo or employee_sudo.id != slot_sudo.employee_id.id:
return request.not_found()
planning_sudo = request.env['planning.planning'].sudo().search([('access_token', '=', token_planning)], limit=1)
if not planning_sudo or slot_sudo.id not in planning_sudo.slot_ids._ids:
return request.not_found()
slot_sudo.write({'employee_id': False})
return redirect('/planning/%s/%s?message=%s' % (token_planning, token_employee, 'unassign'))
@staticmethod
def _format_planning_shifts(color_code):
"""Take a color code from Odoo's Kanban view and returns an hex code compatible with the fullcalendar library"""
switch_color = {
0: '#008784', # No color (doesn't work actually...)
1: '#EE4B39', # Red
2: '#F29648', # Orange
3: '#F4C609', # Yellow
4: '#55B7EA', # Light blue
5: '#71405B', # Dark purple
6: '#E86869', # Salmon pink
7: '#008784', # Medium blue
8: '#267283', # Dark blue
9: '#BF1255', # Fushia
10: '#2BAF73', # Green
11: '#8754B0' # Purple
}
return switch_color[color_code]

118
planning/data/mail_data.xml Normal file
View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="email_template_slot_single" model="mail.template">
<field name="name">Planning: new schedule (single shift)</field>
<field name="email_from">${(object.company_id.email or '')}</field>
<field name="subject">Planning: new schedule (single shift)</field>
<field name="email_to">${object.employee_id.work_email}</field>
<field name="model_id" ref="model_planning_slot"/>
<field name="auto_delete" eval="False"/>
<field name="body_html" type="html">
<div>
<p>Dear ${object.employee_id.name or ''},</p><br/>
<p>You have been assigned the following schedule:</p><br/>
<table style="table-layout: fixed; width: 80%; margin: auto;">
<tr>
<th style="padding: 5px;text-align: left; width: 15%;">From</th>
<td style="padding: 5px;">${format_datetime(object.start_datetime, tz=object.employee_id.tz)}</td>
</tr>
<tr>
<th style="padding: 5px;text-align: left; width: 15%;">To</th>
<td style="padding: 5px;">${format_datetime(object.end_datetime, tz=object.employee_id.tz)}</td>
</tr>
% if object.role_id
<tr>
<th style="padding: 5px;text-align: left; width: 15%;">Role</th>
<td style="padding: 5px;">${object.role_id.name or ''}</td>
</tr>
% endif
% if object.project_id
<tr>
<th style="padding: 5px;text-align: left; width: 15%;">Project</th>
<td style="padding: 5px;">${object.project_id.name or ''}</td>
</tr>
% endif
% if object.name
<tr>
<th style="padding: 5px;text-align: left; width: 15%;">Note</th>
<td style="padding: 5px;">${object.name or ''}</td>
</tr>
% endif
</table>
<div style="text-align: center">
% if ctx.get('render_link')
<div style="display: inline-block; margin: 15px; text-align: center">
<a href="${ctx.unavailable_path}${object.employee_id.employee_token}" target="_blank"
style="padding: 5px 10px; color: #875A7B; text-decoration: none; background-color: #FFFFFF; border: 1px solid #FFFFFF; border-radius: 3px"
>I am unavailable</a>
</div>
% endif
% if ctx.get('render_link')
<div style="display: inline-block; margin: 15px; text-align: center">
<a href="/web?#action=${ctx.get('action_id')}&amp;model=planning.slot&amp;menu_id=${ctx.get('menu_id')}&amp;db=${'dbname' in ctx and ctx['dbname'] or '' }" target="_blank"
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px"
>View Planning</a>
</div>
% endif
</div>
</div>
</field>
</record>
<record id="email_template_planning_planning" model="mail.template">
<field name="name">Planning: new schedule (multiple shifts)</field>
<field name="email_from">${(object.company_id.email or '')}</field>
<field name="subject">Your planning from ${format_datetime(object.start_datetime, tz=ctx.get('employee').tz or 'UTC', dt_format='short')} to ${format_datetime(object.end_datetime, tz=employee.tz if employee else 'UTC', dt_format='short')}</field>
<field name="email_to"></field><!-- Set in the code -->
<field name="model_id" ref="model_planning_planning"/>
<field name="auto_delete" eval="False"/><!-- TODO JEM change this as we are testing -->
<field name="body_html" type="html">
<div>
% if ctx.get('employee'):
<p>Dear ${ctx['employee'].name},</p>
% else:
<p>Hello,</p>
% endif
<p>
You have been assigned new shifts:
</p>
<table style="table-layout: fixed; width: 80%; margin: auto;">
<tr>
<th style="padding: 5px;text-align: left; width: 15%;">From</th>
<td style="padding: 5px;">${format_datetime(object.start_datetime, tz=ctx.get('employee').tz or 'UTC', dt_format='short')}</td>
</tr>
<tr>
<th style="padding: 5px;text-align: left; width: 15%;">To</th>
<td style="padding: 5px;">${format_datetime(object.end_datetime, tz=ctx.get('employee').tz or 'UTC', dt_format='short')}</td>
</tr>
% if object.project_id
<tr>
<th style="padding: 5px;text-align: left; width: 15%;">Project</th>
<td style="padding: 5px;">${object.project_id.name or ''}</td>
</tr>
% endif
</table>
% if ctx.get('planning_url'):
<div style="margin: 15px;">
<a href="${ctx.get('planning_url')}" target="_blank"
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">View Your Planning</a>
</div>
% endif
% if ctx.get('slot_unassigned_count'):
<p>There are new open shifts available. Please assign yourself if you are available.</p>
% endif
% if ctx.get('message'):
<p>${ctx['message']}</p>
% endif
</div>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<record id="ir_cron_forecast_schedule" model="ir.cron">
<field name="name">Planning: generate next recurring shifts</field>
<field name="model_id" ref="model_planning_recurrency"/>
<field name="state">code</field>
<field name="code">model._cron_schedule_next()</field>
<field name="interval_type">weeks</field>
<field name="numbercall">-1</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,547 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Project forecast will warn if the user assigned to a forecast doesn't have a timezone -->
<record id="base.user_demo" model="res.users">
<field name="tz">Europe/Brussels</field>
<field name="groups_id" eval="[(4,ref('planning.group_planning_user'))]"/>
</record>
<!-- Roles -->
<record id="planning_role_bartender" model="planning.role">
<field name="name">Bartender</field>
<field name="color">2</field>
</record>
<record id="planning_role_waiter" model="planning.role">
<field name="name">Waiter</field>
<field name="color">3</field>
</record>
<record id="planning_role_chef" model="planning.role">
<field name="name">Chef</field>
<field name="color">4</field>
</record>
<!-- Shift templates (morning shifts) -->
<record id="planning_template_chef_morning" model="planning.slot.template">
<field name="role_id" ref="planning_role_chef"/>
<field name="start_time" eval="8"/>
<field name="duration" eval="6"/>
</record>
<record id="planning_template_bartender_morning" model="planning.slot.template">
<field name="role_id" ref="planning_role_bartender"/>
<field name="start_time" eval="8"/>
<field name="duration" eval="8"/>
</record>
<record id="planning_template_waiter_morning" model="planning.slot.template">
<field name="role_id" ref="planning_role_waiter"/>
<field name="start_time" eval="8"/>
<field name="duration" eval="8"/>
</record>
<!-- Shift templates (evening shifts) -->
<record id="planning_template_chef_evening" model="planning.slot.template">
<field name="role_id" ref="planning_role_chef"/>
<field name="start_time" eval="14"/>
<field name="duration" eval="8"/>
</record>
<record id="planning_template_bartender_evening" model="planning.slot.template">
<field name="role_id" ref="planning_role_bartender"/>
<field name="start_time" eval="16"/>
<field name="duration" eval="8"/>
</record>
<record id="planning_template_waiter_evening" model="planning.slot.template">
<field name="role_id" ref="planning_role_waiter"/>
<field name="start_time" eval="16"/>
<field name="duration" eval="8"/>
</record>
<!-- Recurrencies -->
<record id="planning_recurrency_1" model="planning.recurrency">
<field name="repeat_interval" eval="1"/>
<field name="repeat_type">forever</field>
</record>
<record id="planning_recurrency_2" model="planning.recurrency">
<field name="repeat_interval" eval="1"/>
<field name="repeat_type">forever</field>
</record>
<record id="planning_recurrency_3" model="planning.recurrency">
<field name="repeat_interval" eval="1"/>
<field name="repeat_type">forever</field>
</record>
<!-- Week 1 -->
<!-- Monday morning shifts -->
<record id="planning_slot_111" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_112" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 12:00:00')"/>
<field name="employee_id" ref="hr.employee_stw"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_113" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_mit"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Monday evening shifts -->
<record id="planning_slot_114" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 20:00:00')"/>
<field name="employee_id" ref="hr.employee_jth"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_115" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_chs"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_116" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_niv"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Tuesday morning shifts -->
<record id="planning_slot_121" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_122" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 12:00:00')"/>
<field name="employee_id" eval="False"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="name" eval="'Check with Katy from the employment agency if they have someone to replace Randall this day'"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_123" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_jep"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Tuesday evening shifts -->
<record id="planning_slot_124" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 20:00:00')"/>
<field name="employee_id" ref="hr.employee_jth"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_125" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_chs"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_126" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_niv"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Wednesday morning shifts -->
<record id="planning_slot_131" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_132" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 12:00:00')"/>
<field name="employee_id" ref="hr.employee_stw"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_133" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_mit"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Wednesday evening shifts -->
<record id="planning_slot_134" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 20:00:00')"/>
<field name="employee_id" ref="hr.employee_jth"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_135" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="name" eval="'Mitchel is replacing Jennie until the end of the week'"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_136" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_niv"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Thursday morning shifts -->
<record id="planning_slot_141" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_142" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 12:00:00')"/>
<field name="employee_id" ref="hr.employee_stw"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_143" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_mit"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Thursday evening shifts -->
<record id="planning_slot_144" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 20:00:00')"/>
<field name="employee_id" ref="hr.employee_jth"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_145" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_146" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_niv"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Friday morning shifts -->
<record id="planning_slot_151" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" eval="False"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_152" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 12:00:00')"/>
<field name="employee_id" ref="hr.employee_stw"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_153" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_mit"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Friday evening shifts -->
<record id="planning_slot_154" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 20:00:00')"/>
<field name="employee_id" ref="hr.employee_jth"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_155" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_156" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_niv"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Week 2 -->
<!-- Monday morning shifts -->
<record id="planning_slot_211" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_212" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 12:00:00')"/>
<field name="employee_id" ref="hr.employee_stw"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_213" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_mit"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Monday evening shifts -->
<record id="planning_slot_214" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 20:00:00')"/>
<field name="employee_id" ref="hr.employee_jth"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_215" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_chs"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_216" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_niv"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Tuesday morning shifts -->
<record id="planning_slot_221" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_222" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 12:00:00')"/>
<field name="employee_id" ref="hr.employee_stw"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_223" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_jep"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Tuesday evening shifts -->
<record id="planning_slot_224" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 20:00:00')"/>
<field name="employee_id" ref="hr.employee_jth"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_225" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_chs"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_226" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" eval="False"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Wednesday morning shifts -->
<record id="planning_slot_231" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_232" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 12:00:00')"/>
<field name="employee_id" ref="hr.employee_stw"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_233" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_mit"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Wednesday evening shifts -->
<record id="planning_slot_234" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 20:00:00')"/>
<field name="employee_id" ref="hr.employee_jth"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_235" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_chs"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_236" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" eval="False"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Thursday morning shifts -->
<record id="planning_slot_241" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_242" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 12:00:00')"/>
<field name="employee_id" ref="hr.employee_stw"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_243" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_mit"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Thursday evening shifts -->
<record id="planning_slot_244" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 20:00:00')"/>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="name" eval="'Ask Toni if she can show Mitchel how things are done in the kitchen because it is her first time working as a chef'"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_245" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_chs"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_246" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_niv"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Friday morning shifts -->
<record id="planning_slot_251" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="recurrency_id" ref="planning_recurrency_1"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_252" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 12:00:00')"/>
<field name="employee_id" ref="hr.employee_stw"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="recurrency_id" ref="planning_recurrency_2"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_253" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 06:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="employee_id" eval="False"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Friday evening shifts -->
<record id="planning_slot_254" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 20:00:00')"/>
<field name="employee_id" ref="hr.employee_admin"/>
<field name="role_id" ref="planning_role_chef"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_255" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_chs"/>
<field name="role_id" ref="planning_role_waiter"/>
<field name="recurrency_id" ref="planning_recurrency_3"/>
<field name="publication_warning" eval="False"/>
</record>
<record id="planning_slot_256" model="planning.slot">
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 14:00:00')"/>
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 22:00:00')"/>
<field name="employee_id" ref="hr.employee_niv"/>
<field name="role_id" ref="planning_role_bartender"/>
<field name="publication_warning" eval="False"/>
</record>
<!-- Duplicate shifts that have a recurrence set -->
<function model="planning.recurrency" name="_repeat_slot" eval="[[ref('planning_recurrency_1'), ref('planning_recurrency_2'), ref('planning_recurrency_3')]]"/>
</data>
</odoo>

1175
planning/i18n/ar.po Normal file

File diff suppressed because it is too large Load Diff

1166
planning/i18n/az.po Normal file

File diff suppressed because it is too large Load Diff

1162
planning/i18n/az_AZ.po Normal file

File diff suppressed because it is too large Load Diff

1172
planning/i18n/bg.po Normal file

File diff suppressed because it is too large Load Diff

1212
planning/i18n/cs.po Normal file

File diff suppressed because it is too large Load Diff

1175
planning/i18n/da.po Normal file

File diff suppressed because it is too large Load Diff

1175
planning/i18n/de.po Normal file

File diff suppressed because it is too large Load Diff

1168
planning/i18n/el.po Normal file

File diff suppressed because it is too large Load Diff

1193
planning/i18n/es.po Normal file

File diff suppressed because it is too large Load Diff

1176
planning/i18n/fi.po Normal file

File diff suppressed because it is too large Load Diff

1288
planning/i18n/fr.po Normal file

File diff suppressed because it is too large Load Diff

1271
planning/i18n/he.po Normal file

File diff suppressed because it is too large Load Diff

1175
planning/i18n/hr.po Normal file

File diff suppressed because it is too large Load Diff

1174
planning/i18n/hu.po Normal file

File diff suppressed because it is too large Load Diff

1174
planning/i18n/id.po Normal file

File diff suppressed because it is too large Load Diff

1172
planning/i18n/it.po Normal file

File diff suppressed because it is too large Load Diff

1170
planning/i18n/ja.po Normal file

File diff suppressed because it is too large Load Diff

1262
planning/i18n/ko.po Normal file

File diff suppressed because it is too large Load Diff

1166
planning/i18n/lb.po Normal file

File diff suppressed because it is too large Load Diff

1173
planning/i18n/lt.po Normal file

File diff suppressed because it is too large Load Diff

1170
planning/i18n/lv.po Normal file

File diff suppressed because it is too large Load Diff

1166
planning/i18n/ml.po Normal file

File diff suppressed because it is too large Load Diff

1172
planning/i18n/mn.po Normal file

File diff suppressed because it is too large Load Diff

1169
planning/i18n/my.po Normal file

File diff suppressed because it is too large Load Diff

1169
planning/i18n/nb.po Normal file

File diff suppressed because it is too large Load Diff

1288
planning/i18n/nl.po Normal file

File diff suppressed because it is too large Load Diff

1176
planning/i18n/pl.po Normal file

File diff suppressed because it is too large Load Diff

1162
planning/i18n/planning.pot Normal file

File diff suppressed because it is too large Load Diff

1174
planning/i18n/pt.po Normal file

File diff suppressed because it is too large Load Diff

1176
planning/i18n/pt_BR.po Normal file

File diff suppressed because it is too large Load Diff

1168
planning/i18n/ro.po Normal file

File diff suppressed because it is too large Load Diff

1172
planning/i18n/ru.po Normal file

File diff suppressed because it is too large Load Diff

1171
planning/i18n/sk.po Normal file

File diff suppressed because it is too large Load Diff

1172
planning/i18n/sl.po Normal file

File diff suppressed because it is too large Load Diff

1167
planning/i18n/sr.po Normal file

File diff suppressed because it is too large Load Diff

1174
planning/i18n/sv.po Normal file

File diff suppressed because it is too large Load Diff

1176
planning/i18n/tr.po Normal file

File diff suppressed because it is too large Load Diff

1279
planning/i18n/uk.po Normal file

File diff suppressed because it is too large Load Diff

1162
planning/i18n/uz.po Normal file

File diff suppressed because it is too large Load Diff

1279
planning/i18n/vi.po Normal file

File diff suppressed because it is too large Load Diff

1176
planning/i18n/zh_CN.po Normal file

File diff suppressed because it is too large Load Diff

1171
planning/i18n/zh_TW.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import hr
from . import planning_recurrency
from . import planning
from . import planning_template
from . import res_company
from . import res_config_settings

44
planning/models/hr.py Normal file
View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import uuid
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
class Employee(models.Model):
_inherit = "hr.employee"
def _default_employee_token(self):
return str(uuid.uuid4())
planning_role_id = fields.Many2one('planning.role', string="Default Planning Role", groups='hr.group_hr_user')
employee_token = fields.Char('Security Token', default=_default_employee_token, copy=False, groups='hr.group_hr_user', readonly=True)
_sql_constraints = [
('employee_token_unique', 'unique(employee_token)', 'Error: each employee token must be unique')
]
def _init_column(self, column_name):
# to avoid generating a single default employee_token when installing the module,
# we need to set the default row by row for this column
if column_name == "employee_token":
_logger.debug("Table '%s': setting default value of new column %s to unique values for each row", self._table, column_name)
self.env.cr.execute("SELECT id FROM %s WHERE employee_token IS NULL" % self._table)
acc_ids = self.env.cr.dictfetchall()
query_list = [{'id': acc_id['id'], 'employee_token': self._default_employee_token()} for acc_id in acc_ids]
query = 'UPDATE ' + self._table + ' SET employee_token = %(employee_token)s WHERE id = %(id)s;'
self.env.cr._obj.executemany(query, query_list)
else:
super(Employee, self)._init_column(column_name)
def _planning_get_url(self, planning):
result = {}
for employee in self:
if employee.user_id and employee.user_id.has_group('planning.group_planning_user'):
result[employee.id] = '/web?#action=planning.planning_action_open_shift'
else:
result[employee.id] = '/planning/%s/%s' % (planning.access_token, employee.employee_token)
return result

680
planning/models/planning.py Normal file
View File

@ -0,0 +1,680 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from ast import literal_eval
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
import json
import logging
import pytz
import uuid
from math import ceil
from odoo import api, fields, models, _
from odoo.exceptions import UserError, AccessError
from odoo.osv import expression
from odoo.tools.safe_eval import safe_eval
from odoo.tools import format_time
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
_logger = logging.getLogger(__name__)
def days_span(start_datetime, end_datetime):
if not isinstance(start_datetime, datetime):
raise ValueError
if not isinstance(end_datetime, datetime):
raise ValueError
end = datetime.combine(end_datetime, datetime.min.time())
start = datetime.combine(start_datetime, datetime.min.time())
duration = end - start
return duration.days + 1
class Planning(models.Model):
_name = 'planning.slot'
_description = 'Planning Shift'
_order = 'start_datetime,id desc'
_rec_name = 'name'
_check_company_auto = True
def _default_employee_id(self):
return self.env['hr.employee'].search([('user_id', '=', self.env.uid), ('company_id', '=', self.env.company.id)])
def _default_start_datetime(self):
return fields.Datetime.to_string(datetime.combine(fields.Datetime.now(), datetime.min.time()))
def _default_end_datetime(self):
return fields.Datetime.to_string(datetime.combine(fields.Datetime.now(), datetime.max.time()))
name = fields.Text('Note')
employee_id = fields.Many2one('hr.employee', "Employee", default=_default_employee_id, group_expand='_read_group_employee_id', check_company=True)
user_id = fields.Many2one('res.users', string="User", related='employee_id.user_id', store=True, readonly=True)
company_id = fields.Many2one('res.company', string="Company", required=True, compute="_compute_planning_slot_company_id", store=True, readonly=False)
role_id = fields.Many2one('planning.role', string="Role")
color = fields.Integer("Color", related='role_id.color')
was_copied = fields.Boolean("This shift was copied from previous week", default=False, readonly=True)
start_datetime = fields.Datetime("Start Date", required=True, default=_default_start_datetime)
end_datetime = fields.Datetime("End Date", required=True, default=_default_end_datetime)
# UI fields and warnings
allow_self_unassign = fields.Boolean('Let employee unassign themselves', related='company_id.planning_allow_self_unassign')
is_assigned_to_me = fields.Boolean('Is this shift assigned to the current user', compute='_compute_is_assigned_to_me')
overlap_slot_count = fields.Integer('Overlapping slots', compute='_compute_overlap_slot_count')
# time allocation
allocation_type = fields.Selection([
('planning', 'Planning'),
('forecast', 'Forecast')
], compute='_compute_allocation_type')
allocated_hours = fields.Float("Allocated hours", default=0, compute='_compute_allocated_hours', store=True)
allocated_percentage = fields.Float("Allocated Time (%)", default=100, help="Percentage of time the employee is supposed to work during the shift.")
working_days_count = fields.Integer("Number of working days", compute='_compute_working_days_count', store=True)
# publication and sending
is_published = fields.Boolean("Is the shift sent", default=False, readonly=True, help="If checked, this means the planning entry has been sent to the employee. Modifying the planning entry will mark it as not sent.")
publication_warning = fields.Boolean("Modified since last publication", default=False, readonly=True, help="If checked, it means that the shift contains has changed since its last publish.", copy=False)
# template dummy fields (only for UI purpose)
template_creation = fields.Boolean("Save as a Template", default=False, store=False, inverse='_inverse_template_creation')
template_autocomplete_ids = fields.Many2many('planning.slot.template', store=False, compute='_compute_template_autocomplete_ids')
template_id = fields.Many2one('planning.slot.template', string='Planning Templates', store=False)
# Recurring (`repeat_` fields are none stored, only used for UI purpose)
recurrency_id = fields.Many2one('planning.recurrency', readonly=True, index=True, ondelete="set null", copy=False)
repeat = fields.Boolean("Repeat", compute='_compute_repeat', inverse='_inverse_repeat')
repeat_interval = fields.Integer("Repeat every", default=1, compute='_compute_repeat', inverse='_inverse_repeat')
repeat_type = fields.Selection([('forever', 'Forever'), ('until', 'Until')], string='Repeat Type', default='forever', compute='_compute_repeat', inverse='_inverse_repeat')
repeat_until = fields.Date("Repeat Until", compute='_compute_repeat', inverse='_inverse_repeat', help="If set, the recurrence stop at that date. Otherwise, the recurrence is applied indefinitely.")
_sql_constraints = [
('check_start_date_lower_end_date', 'CHECK(end_datetime > start_datetime)', 'Shift end date should be greater than its start date'),
('check_allocated_hours_positive', 'CHECK(allocated_hours >= 0)', 'You cannot have negative shift'),
]
@api.depends('employee_id')
def _compute_planning_slot_company_id(self):
if self.employee_id:
self.company_id = self.employee_id.company_id.id
if not self.company_id.id:
self.company_id = self.env.company
@api.depends('user_id')
def _compute_is_assigned_to_me(self):
for slot in self:
slot.is_assigned_to_me = slot.user_id == self.env.user
@api.depends('start_datetime', 'end_datetime')
def _compute_allocation_type(self):
for slot in self:
if slot.start_datetime and slot.end_datetime and (slot.end_datetime - slot.start_datetime).total_seconds() / 3600.0 < 24:
slot.allocation_type = 'planning'
else:
slot.allocation_type = 'forecast'
@api.depends('start_datetime', 'end_datetime', 'employee_id.resource_calendar_id', 'allocated_percentage')
def _compute_allocated_hours(self):
for slot in self:
if slot.start_datetime and slot.end_datetime:
percentage = slot.allocated_percentage / 100.0 or 1
if slot.allocation_type == 'planning' and slot.start_datetime and slot.end_datetime:
slot.allocated_hours = (slot.end_datetime - slot.start_datetime).total_seconds() * percentage / 3600.0
else:
if slot.employee_id:
slot.allocated_hours = slot.employee_id._get_work_days_data(slot.start_datetime, slot.end_datetime, compute_leaves=True)['hours'] * percentage
else:
slot.allocated_hours = 0.0
@api.depends('start_datetime', 'end_datetime', 'employee_id')
def _compute_working_days_count(self):
for slot in self:
if slot.employee_id:
slot.working_days_count = ceil(slot.employee_id._get_work_days_data(slot.start_datetime, slot.end_datetime, compute_leaves=True)['days'])
else:
slot.working_days_count = 0
@api.depends('start_datetime', 'end_datetime', 'employee_id')
def _compute_overlap_slot_count(self):
if self.ids:
self.flush(['start_datetime', 'end_datetime', 'employee_id'])
query = """
SELECT S1.id,count(*) FROM
planning_slot S1, planning_slot S2
WHERE
S1.start_datetime < S2.end_datetime and S1.end_datetime > S2.start_datetime and S1.id <> S2.id and S1.employee_id = S2.employee_id
GROUP BY S1.id;
"""
self.env.cr.execute(query, (tuple(self.ids),))
overlap_mapping = dict(self.env.cr.fetchall())
for slot in self:
slot.overlap_slot_count = overlap_mapping.get(slot.id, 0)
else:
self.overlap_slot_count = 0
@api.depends('role_id')
def _compute_template_autocomplete_ids(self):
domain = []
if self.role_id:
domain = [('role_id', '=', self.role_id.id)]
self.template_autocomplete_ids = self.env['planning.slot.template'].search(domain, order='start_time', limit=10)
@api.depends('recurrency_id')
def _compute_repeat(self):
for slot in self:
if slot.recurrency_id:
slot.repeat = True
slot.repeat_interval = slot.recurrency_id.repeat_interval
slot.repeat_until = slot.recurrency_id.repeat_until
slot.repeat_type = slot.recurrency_id.repeat_type
else:
slot.repeat = False
slot.repeat_interval = False
slot.repeat_until = False
slot.repeat_type = False
def _inverse_repeat(self):
for slot in self:
if slot.repeat and not slot.recurrency_id.id: # create the recurrence
recurrency_values = {
'repeat_interval': slot.repeat_interval,
'repeat_until': slot.repeat_until if slot.repeat_type == 'until' else False,
'repeat_type': slot.repeat_type,
'company_id': slot.company_id.id,
}
recurrence = self.env['planning.recurrency'].create(recurrency_values)
slot.recurrency_id = recurrence
slot.recurrency_id._repeat_slot()
# user wants to delete the recurrence
# here we also check that we don't delete by mistake a slot of which the repeat parameters have been changed
elif not slot.repeat and slot.recurrency_id.id and (
slot.repeat_type == slot.recurrency_id.repeat_type and
slot.repeat_until == slot.recurrency_id.repeat_until and
slot.repeat_interval == slot.recurrency_id.repeat_interval
):
slot.recurrency_id._delete_slot(slot.end_datetime)
slot.recurrency_id.unlink() # will set recurrency_id to NULL
def _inverse_template_creation(self):
values_list = []
existing_values = []
for slot in self:
if slot.template_creation:
values_list.append(slot._prepare_template_values())
# Here we check if there's already a template w/ the same data
existing_templates = self.env['planning.slot.template'].read_group([], ['role_id', 'start_time', 'duration'], ['role_id', 'start_time', 'duration'], limit=None, lazy=False)
if len(existing_templates):
for element in existing_templates:
role_id = element['role_id'][0] if element.get('role_id') else False
existing_values.append({'role_id': role_id, 'start_time': element['start_time'], 'duration': element['duration']})
self.env['planning.slot.template'].create([x for x in values_list if x not in existing_values])
@api.onchange('employee_id')
def _onchange_employee_id(self):
if self.employee_id:
start = self.start_datetime or datetime.combine(fields.Datetime.now(), datetime.min.time())
end = self.end_datetime or datetime.combine(fields.Datetime.now(), datetime.max.time())
work_interval = self.employee_id.resource_id._get_work_interval(start, end)
start_datetime, end_datetime = work_interval[self.employee_id.resource_id]
#start_datetime, end_datetime = work_interval[self.employee_id.resource_id.id]
if start_datetime:
self.start_datetime = start_datetime.astimezone(pytz.utc).replace(tzinfo=None)
if end_datetime:
self.end_datetime = end_datetime.astimezone(pytz.utc).replace(tzinfo=None)
# Set default role if the role field is empty
if not self.role_id and self.employee_id.sudo().planning_role_id:
self.role_id = self.employee_id.sudo().planning_role_id
@api.onchange('start_datetime', 'end_datetime', 'employee_id')
def _onchange_dates(self):
if self.employee_id and self.is_published:
self.publication_warning = True
@api.onchange('template_creation')
def _onchange_template_autocomplete_ids(self):
templates = self.env['planning.slot.template'].search([], order='start_time', limit=10)
if templates:
if not self.template_creation:
self.template_autocomplete_ids = templates
else:
self.template_autocomplete_ids = False
else:
self.template_autocomplete_ids = False
@api.onchange('template_id')
def _onchange_template_id(self):
user_tz = pytz.timezone(self.env.user.tz or 'UTC')
if self.template_id and self.start_datetime:
h, m = divmod(self.template_id.start_time, 1)
start = pytz.utc.localize(self.start_datetime).astimezone(user_tz)
start = start.replace(hour=int(h), minute=int(m * 60))
self.start_datetime = start.astimezone(pytz.utc).replace(tzinfo=None)
h, m = divmod(self.template_id.duration, 1)
delta = timedelta(hours=int(h), minutes=int(m * 60))
self.end_datetime = fields.Datetime.to_string(self.start_datetime + delta)
self.role_id = self.template_id.role_id
@api.onchange('repeat')
def _onchange_default_repeat_values(self):
""" When checking the `repeat` flag on an existing record, the values of recurring fields are `False`. This onchange
restore the default value for usability purpose.
"""
recurrence_fields = ['repeat_interval', 'repeat_until', 'repeat_type']
default_values = self.default_get(recurrence_fields)
for fname in recurrence_fields:
self[fname] = default_values.get(fname)
# ----------------------------------------------------
# ORM overrides
# ----------------------------------------------------
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
result = super(Planning, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
view_type = self.env.context.get('view_type')
if 'employee_id' in groupby and view_type == 'gantt':
# Always prepend 'Undefined Employees' (will be printed 'Open Shifts' when called by the frontend)
d = {}
for field in fields:
d.update({field: False})
result.insert(0, d)
return result
def name_get(self):
group_by = self.env.context.get('group_by', [])
field_list = [fname for fname in self._name_get_fields() if fname not in group_by][:2] # limit to 2 labels
result = []
for slot in self:
# label part, depending on context `groupby`
name = ' - '.join([self._fields[fname].convert_to_display_name(slot[fname], slot) for fname in field_list if slot[fname]])
# date / time part
destination_tz = pytz.timezone(self.env.user.tz or 'UTC')
start_datetime = pytz.utc.localize(slot.start_datetime).astimezone(destination_tz).replace(tzinfo=None)
end_datetime = pytz.utc.localize(slot.end_datetime).astimezone(destination_tz).replace(tzinfo=None)
if slot.end_datetime - slot.start_datetime <= timedelta(hours=24): # shift on a single day
name = '%s - %s %s' % (
format_time(self.env, start_datetime.time(), time_format='short'),
format_time(self.env, end_datetime.time(), time_format='short'),
name
)
else:
name = '%s - %s %s' % (
start_datetime.date(),
end_datetime.date(),
name
)
# add unicode bubble to tell there is a note
if slot.name:
name = u'%s \U0001F4AC' % name
result.append([slot.id, name])
return result
@api.model
def create(self, vals):
if not vals.get('company_id') and vals.get('employee_id'):
vals['company_id'] = self.env['hr.employee'].browse(vals.get('employee_id')).company_id.id
if not vals.get('company_id'):
vals['company_id'] = self.env.company.id
return super().create(vals)
def write(self, values):
# detach planning entry from recurrency
if any(fname in values.keys() for fname in self._get_fields_breaking_recurrency()) and not values.get('recurrency_id'):
values.update({'recurrency_id': False})
# warning on published shifts
if 'publication_warning' not in values and (set(values.keys()) & set(self._get_fields_breaking_publication())):
values['publication_warning'] = True
result = super(Planning, self).write(values)
# recurrence
if any(key in ('repeat', 'repeat_type', 'repeat_until', 'repeat_interval') for key in values):
# User is trying to change this record's recurrence so we delete future slots belonging to recurrence A
# and we create recurrence B from now on w/ the new parameters
for slot in self:
if slot.recurrency_id and values.get('repeat') is None:
recurrency_values = {
'repeat_interval': values.get('repeat_interval') or slot.recurrency_id.repeat_interval,
'repeat_until': values.get('repeat_until') if values.get('repeat_type') == 'until' else False,
'repeat_type': values.get('repeat_type'),
'company_id': slot.company_id.id,
}
# Kill recurrence A
slot.recurrency_id.repeat_type = 'until'
slot.recurrency_id.repeat_until = slot.start_datetime
slot.recurrency_id._delete_slot(slot.end_datetime)
# Create recurrence B
recurrence = slot.env['planning.recurrency'].create(recurrency_values)
slot.recurrency_id = recurrence
slot.recurrency_id._repeat_slot()
return result
# ----------------------------------------------------
# Actions
# ----------------------------------------------------
def action_unlink(self):
self.unlink()
return {'type': 'ir.actions.act_window_close'}
def action_see_overlaping_slots(self):
domain_map = self._get_overlap_domain()
return {
'type': 'ir.actions.act_window',
'res_model': 'planning.slot',
'name': _('Shifts in conflict'),
'view_mode': 'gantt,list,form',
'domain': domain_map[self.id],
'context': {
'initialDate': min([slot.start_datetime for slot in self.search(domain_map[self.id])])
}
}
def action_self_assign(self):
""" Allow planning user to self assign open shift. """
self.ensure_one()
# user must at least 'read' the shift to self assign (Prevent any user in the system (portal, ...) to assign themselves)
if not self.check_access_rights('read', raise_exception=False):
raise AccessError(_("You don't the right to self assign."))
if self.employee_id:
raise UserError(_("You can not assign yourself to an already assigned shift."))
return self.sudo().write({'employee_id': self.env.user.employee_id.id if self.env.user.employee_id else False})
def action_self_unassign(self):
""" Allow planning user to self unassign from a shift, if the feature is activated """
self.ensure_one()
# The following condition will check the read access on planning.slot, and that user must at least 'read' the
# shift to self unassign. Prevent any user in the system (portal, ...) to unassign any shift.
if not self.allow_self_unassign:
raise UserError(_("The company does not allow you to self unassign."))
if self.employee_id != self.env.user.employee_id:
raise UserError(_("You can not unassign another employee than yourself."))
return self.sudo().write({'employee_id': False})
# ----------------------------------------------------
# Gantt view
# ----------------------------------------------------
@api.model
def gantt_unavailability(self, start_date, end_date, scale, group_bys=None, rows=None):
start_datetime = fields.Datetime.from_string(start_date)
end_datetime = fields.Datetime.from_string(end_date)
employee_ids = set()
# function to "mark" top level rows concerning employees
# the propagation of that item to subrows is taken care of in the traverse function below
def tag_employee_rows(rows):
for row in rows:
group_bys = row.get('groupedBy')
res_id = row.get('resId')
if group_bys:
# if employee_id is the first grouping attribute, we mark the row
if group_bys[0] == 'employee_id' and res_id:
employee_id = res_id
employee_ids.add(employee_id)
row['employee_id'] = employee_id
# else we recursively traverse the rows where employee_id appears in the group_by
elif 'employee_id' in group_bys:
tag_employee_rows(row.get('rows'))
tag_employee_rows(rows)
employees = self.env['hr.employee'].browse(employee_ids)
leaves_mapping = employees.mapped('resource_id')._get_unavailable_intervals(start_datetime, end_datetime)
# function to recursively replace subrows with the ones returned by func
def traverse(func, row):
new_row = dict(row)
if new_row.get('employee_id'):
for sub_row in new_row.get('rows'):
sub_row['employee_id'] = new_row['employee_id']
new_row['rows'] = [traverse(func, row) for row in new_row.get('rows')]
return func(new_row)
cell_dt = timedelta(hours=1) if scale in ['day', 'week'] else timedelta(hours=12)
# for a single row, inject unavailability data
def inject_unavailability(row):
new_row = dict(row)
if row.get('employee_id'):
employee_id = self.env['hr.employee'].browse(row.get('employee_id'))
if employee_id:
# remove intervals smaller than a cell, as they will cause half a cell to turn grey
# ie: when looking at a week, a employee start everyday at 8, so there is a unavailability
# like: 2019-05-22 20:00 -> 2019-05-23 08:00 which will make the first half of the 23's cell grey
notable_intervals = filter(lambda interval: interval[1] - interval[0] >= cell_dt, leaves_mapping[employee_id.resource_id.id])
new_row['unavailabilities'] = [{'start': interval[0], 'stop': interval[1]} for interval in notable_intervals]
return new_row
return [traverse(inject_unavailability, row) for row in rows]
# ----------------------------------------------------
# Period Duplication
# ----------------------------------------------------
@api.model
def action_copy_previous_week(self, date_start_week):
date_end_copy = datetime.strptime(date_start_week, DEFAULT_SERVER_DATETIME_FORMAT)
date_start_copy = date_end_copy - relativedelta(days=7)
domain = [
('start_datetime', '>=', date_start_copy),
('end_datetime', '<=', date_end_copy),
('recurrency_id', '=', False),
('was_copied', '=', False)
]
slots_to_copy = self.search(domain)
new_slot_values = []
for slot in slots_to_copy:
if not slot.was_copied:
values = slot.copy_data()[0]
if values.get('start_datetime'):
values['start_datetime'] += relativedelta(days=7)
if values.get('end_datetime'):
values['end_datetime'] += relativedelta(days=7)
values['is_published'] = False
new_slot_values.append(values)
slots_to_copy.write({'was_copied': True})
return self.create(new_slot_values)
# ----------------------------------------------------
# Sending Shifts
# ----------------------------------------------------
def action_send(self):
group_planning_user = self.env.ref('planning.group_planning_user')
template = self.env.ref('planning.email_template_slot_single')
# update context to build a link for view in the slot
view_context = dict(self._context)
view_context.update({
'menu_id': str(self.env.ref('planning.planning_menu_root').id),
'action_id': str(self.env.ref('planning.planning_action_open_shift').id),
'dbname': self.env.cr.dbname,
'render_link': self.employee_id.user_id and self.employee_id.user_id in group_planning_user.users,
'unavailable_path': '/planning/myshifts/',
})
slot_template = template.with_context(view_context)
mails_to_send = self.env['mail.mail']
for slot in self:
if slot.employee_id and slot.employee_id.work_email:
mail_id = slot_template.with_context(view_context).send_mail(slot.id, notif_layout='mail.mail_notification_light')
current_mail = self.env['mail.mail'].browse(mail_id)
mails_to_send |= current_mail
if mails_to_send:
mails_to_send.send()
self.write({
'is_published': True,
'publication_warning': False,
})
return mails_to_send
def action_publish(self):
self.write({
'is_published': True,
'publication_warning': False,
})
return True
# ----------------------------------------------------
# Business Methods
# ----------------------------------------------------
def _name_get_fields(self):
""" List of fields that can be displayed in the name_get """
return ['employee_id', 'role_id']
def _get_fields_breaking_publication(self):
""" Fields list triggering the `publication_warning` to True when updating shifts """
return [
'employee_id',
'start_datetime',
'end_datetime',
'role_id',
]
def _get_fields_breaking_recurrency(self):
"""Returns the list of field which when changed should break the relation of the forecast
with it's recurrency
"""
return [
'employee_id',
'role_id',
]
def _get_overlap_domain(self):
""" get overlapping domain for current shifts
:returns dict : map with slot id as key and domain as value
"""
domain_mapping = {}
for slot in self:
domain_mapping[slot.id] = [
'&',
'&',
('employee_id', '!=', False),
('employee_id', '=', slot.employee_id.id),
'&',
('start_datetime', '<', slot.end_datetime),
('end_datetime', '>', slot.start_datetime)
]
return domain_mapping
def _prepare_template_values(self):
""" extract values from shift to create a template """
# compute duration w/ tzinfo otherwise DST will not be taken into account
destination_tz = pytz.timezone(self.env.user.tz or 'UTC')
start_datetime = pytz.utc.localize(self.start_datetime).astimezone(destination_tz)
end_datetime = pytz.utc.localize(self.end_datetime).astimezone(destination_tz)
# convert time delta to hours and minutes
total_seconds = (end_datetime - start_datetime).total_seconds()
m, s = divmod(total_seconds, 60)
h, m = divmod(m, 60)
return {
'start_time': start_datetime.hour + start_datetime.minute / 60.0,
'duration': h + (m / 60.0),
'role_id': self.role_id.id
}
def _read_group_employee_id(self, employees, domain, order):
if self._context.get('planning_expand_employee'):
return self.env['planning.slot'].search([('create_date', '>', datetime.now() - timedelta(days=30))]).mapped('employee_id')
return employees
class PlanningRole(models.Model):
_name = 'planning.role'
_description = "Planning Role"
_order = 'name,id desc'
_rec_name = 'name'
name = fields.Char('Name', required=True)
color = fields.Integer("Color", default=0)
class PlanningPlanning(models.Model):
_name = 'planning.planning'
_description = 'Planning sent by email'
@api.model
def _default_access_token(self):
return str(uuid.uuid4())
start_datetime = fields.Datetime("Start Date", required=True)
end_datetime = fields.Datetime("Stop Date", required=True)
include_unassigned = fields.Boolean("Includes Open shifts", default=True)
access_token = fields.Char("Security Token", default=_default_access_token, required=True, copy=False, readonly=True)
last_sent_date = fields.Datetime("Last sent date")
slot_ids = fields.Many2many('planning.slot', "Shifts", compute='_compute_slot_ids')
company_id = fields.Many2one('res.company', "Company", required=True, default=lambda self: self.env.company)
_sql_constraints = [
('check_start_date_lower_stop_date', 'CHECK(end_datetime > start_datetime)', 'Planning end date should be greater than its start date'),
]
@api.depends('start_datetime', 'end_datetime')
def _compute_display_name(self):
""" This override is need to have a human readable string in the email light layout header (`message.record_name`) """
for planning in self:
number_days = (planning.end_datetime - planning.start_datetime).days
planning.display_name = _('Planning of %s days') % (number_days,)
@api.depends('start_datetime', 'end_datetime', 'include_unassigned')
def _compute_slot_ids(self):
domain_map = self._get_domain_slots()
for planning in self:
domain = domain_map[planning.id]
if not planning.include_unassigned:
domain = expression.AND([domain, [('employee_id', '!=', False)]])
planning.slot_ids = self.env['planning.slot'].search(domain)
# ----------------------------------------------------
# Business Methods
# ----------------------------------------------------
def _get_domain_slots(self):
result = {}
for planning in self:
domain = ['&', '&', ('start_datetime', '<=', planning.end_datetime), ('end_datetime', '>', planning.start_datetime), ('company_id', '=', planning.company_id.id)]
result[planning.id] = domain
return result
def send_planning(self, message=None):
email_from = self.env.user.email or self.env.user.company_id.email or ''
sent_slots = self.env['planning.slot']
for planning in self:
# prepare planning urls, recipient employees, ...
slots = planning.slot_ids
slots_open = slots.filtered(lambda slot: not slot.employee_id)
# extract planning URLs
employees = slots.mapped('employee_id')
employee_url_map = employees.sudo()._planning_get_url(planning)
# send planning email template with custom domain per employee
template = self.env.ref('planning.email_template_planning_planning', raise_if_not_found=False)
template_context = {
'slot_unassigned_count': len(slots_open),
'slot_total_count': len(slots),
'message': message,
}
if template:
# /!\ For security reason, we only given the public employee to render mail template
for employee in self.env['hr.employee.public'].browse(employees.ids):
if employee.work_email:
template_context['employee'] = employee
template_context['planning_url'] = employee_url_map[employee.id]
template.with_context(**template_context).send_mail(planning.id, email_values={'email_to': employee.work_email, 'email_from': email_from}, notif_layout='mail.mail_notification_light')
sent_slots |= slots
# mark as sent
self.write({'last_sent_date': fields.Datetime.now()})
sent_slots.write({
'is_published': True,
'publication_warning': False
})
return True

View File

@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from odoo import api, fields, models, _
from odoo.tools import get_timedelta
from odoo.exceptions import ValidationError
class PlanningRecurrency(models.Model):
_name = 'planning.recurrency'
_description = "Planning Recurrence"
slot_ids = fields.One2many('planning.slot', 'recurrency_id', string="Related planning entries")
repeat_interval = fields.Integer("Repeat every", default=1, required=True)
repeat_type = fields.Selection([('forever', 'Forever'), ('until', 'Until')], string='weeks', default='forever')
repeat_until = fields.Datetime(string="Repeat until", help="Up to which date should the plannings be repeated")
last_generated_end_datetime = fields.Datetime("Last Generated End Date", readonly=True)
company_id = fields.Many2one('res.company', string="Company", readonly=True, required=True, default=lambda self: self.env.company)
_sql_constraints = [
('check_repeat_interval_positive', 'CHECK(repeat_interval >= 1)', 'Recurrency repeat interval should be at least 1'),
('check_until_limit', "CHECK((repeat_type = 'until' AND repeat_until IS NOT NULL) OR (repeat_type != 'until'))", 'A recurrence repeating itself until a certain date must have its limit set'),
]
@api.constrains('company_id', 'slot_ids')
def _check_multi_company(self):
for recurrency in self:
if not all(recurrency.company_id == planning.company_id for planning in recurrency.slot_ids):
raise ValidationError(_('An shift must be in the same company as its recurrency.'))
def name_get(self):
result = []
for recurrency in self:
if recurrency.repeat_type == 'forever':
name = _('Forever, every %s week(s)') % (recurrency.repeat_interval,)
else:
name = _('Every %s week(s) until %s') % (recurrency.repeat_interval, recurrency.repeat_until)
result.append([recurrency.id, name])
return result
@api.model
def _cron_schedule_next(self):
companies = self.env['res.company'].search([])
now = fields.Datetime.now()
stop_datetime = None
for company in companies:
delta = get_timedelta(company.planning_generation_interval, 'month')
recurrencies = self.search([
'&',
'&',
('company_id', '=', company.id),
('last_generated_end_datetime', '<', now + delta),
'|',
('repeat_until', '=', False),
('repeat_until', '>', now - delta),
])
recurrencies._repeat_slot(now + delta)
def _repeat_slot(self, stop_datetime=False):
for recurrency in self:
slot = self.env['planning.slot'].search([('recurrency_id', '=', recurrency.id)], limit=1, order='start_datetime DESC')
if slot:
# find the end of the recurrence
recurrence_end_dt = False
if recurrency.repeat_type == 'until':
recurrence_end_dt = recurrency.repeat_until
# find end of generation period (either the end of recurrence (if this one ends before the cron period), or the given `stop_datetime` (usually the cron period))
if not stop_datetime:
stop_datetime = fields.Datetime.now() + get_timedelta(recurrency.company_id.planning_generation_interval, 'month')
range_limit = min([dt for dt in [recurrence_end_dt, stop_datetime] if dt])
# generate recurring slots
recurrency_delta = get_timedelta(recurrency.repeat_interval, 'week')
next_start = slot.start_datetime + recurrency_delta
slot_values_list = []
while next_start < range_limit:
slot_values = slot.copy_data({
'start_datetime': next_start,
'end_datetime': next_start + (slot.end_datetime - slot.start_datetime),
'recurrency_id': recurrency.id,
'company_id': recurrency.company_id.id,
'repeat': True,
'is_published': False
})[0]
slot_values_list.append(slot_values)
next_start = next_start + recurrency_delta
self.env['planning.slot'].create(slot_values_list)
recurrency.write({'last_generated_end_datetime': next_start - recurrency_delta})
else:
recurrency.unlink()
def _delete_slot(self, start_datetime):
slots = self.env['planning.slot'].search([('recurrency_id', 'in', self.ids), ('start_datetime', '>=', start_datetime), ('is_published', '=', False)])
slots.unlink()

View File

@ -0,0 +1,35 @@
import math
from datetime import datetime, timedelta, time, date
from odoo import api, fields, models, _
from odoo.tools import format_time
class PlanningTemplate(models.Model):
_name = 'planning.slot.template'
_description = "Shift Template"
role_id = fields.Many2one('planning.role', string="Role")
start_time = fields.Float('Start hour', default=0)
duration = fields.Float('Duration (hours)', default=0)
_sql_constraints = [
('check_start_time_lower_than_24', 'CHECK(start_time <= 24)', 'You cannot have a start hour greater than 24'),
('check_start_time_positive', 'CHECK(start_time >= 0)', 'Start hour must be a positive number'),
('check_duration_positive', 'CHECK(duration >= 0)', 'You cannot have a negative duration')
]
def name_get(self):
result = []
for shift_template in self:
start_time = time(hour=int(shift_template.start_time), minute=round(math.modf(shift_template.start_time)[0] / (1 / 60.0)))
duration = timedelta(hours=int(shift_template.duration), minutes=round(math.modf(shift_template.duration)[0] / (1 / 60.0)))
end_time = datetime.combine(date.today(), start_time) + duration
name = '%s - %s %s %s' % (
format_time(shift_template.env, start_time, time_format='h a' if start_time.minute == 0 else 'h:mm a'),
format_time(shift_template.env, end_time.time(), time_format='h a' if end_time.minute == 0 else 'h:mm a'),
'(%s days span)' % (duration.days + 1) if duration.days > 0 else '',
shift_template.role_id.name if shift_template.role_id.name is not False else ''
)
result.append([shift_template.id, name])
return result

View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class Company(models.Model):
_inherit = 'res.company'
planning_generation_interval = fields.Integer("Rate of shift generation", required=True, readonly=False, default=3, help="Delay for the rate at which recurring shift should be generated in month")
planning_allow_self_unassign = fields.Boolean("Can employee un-assign themselves?", default=False,
help="Let your employees un-assign themselves from shifts when unavailable")

View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
planning_generation_interval = fields.Integer("Rate of shift generation", required=True,
related="company_id.planning_generation_interval", readonly=False, help="Delay for the rate at which recurring shifts should be generated")
planning_allow_self_unassign = fields.Boolean("Allow Unassignment", default=False, readonly=False,
related="company_id.planning_allow_self_unassign", help="Let your employees un-assign themselves from shifts when unavailable")

View File

@ -0,0 +1,4 @@
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import planning_report

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import tools
from odoo import fields, models
class PlanningReport(models.Model):
_name = "planning.slot.report.analysis"
_description = "Planning Statistics"
_auto = False
_rec_name = 'entry_date'
_order = 'entry_date desc'
entry_date = fields.Date('Date', readonly=True)
employee_id = fields.Many2one('hr.employee', 'Employee', readonly=True)
role_id = fields.Many2one('planning.role', string='Role', readonly=True)
company_id = fields.Many2one('res.company', string='Company', readonly=True)
number_hours = fields.Float("Allocated Hours", readonly=True)
def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
# We dont take ressource into account as we would need to move
# the generate_series() in the FROM and use a condition on the
# join to exclude holidays like it's done in timesheet.
self.env.cr.execute("""
CREATE or REPLACE VIEW %s as (
(
SELECT
p.id,
generate_series(start_datetime,end_datetime,'1 day'::interval) entry_date,
p.role_id AS role_id,
p.company_id AS company_id,
p.employee_id AS employee_id,
p.allocated_hours / ((p.end_datetime::date - p.start_datetime::date)+1) AS number_hours
FROM
planning_slot p
)
)
""" % (self._table,))

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="planning_slot_report_view_pivot" model="ir.ui.view">
<field name="name">planning.slot.report.pivot</field>
<field name="model">planning.slot.report.analysis</field>
<field name="arch" type="xml">
<pivot string="Planning Analysis">
<field name="entry_date" interval="month" type="col"/>
<field name="employee_id" type="row"/>
<field name="number_hours" type="measure"/>
</pivot>
</field>
</record>
<record id="planning_slot_report_view_graph" model="ir.ui.view">
<field name="name">planning.slot.report.graph</field>
<field name="model">planning.slot.report.analysis</field>
<field name="arch" type="xml">
<graph string="Planning Analysis" type="bar">
<field name="entry_date" type="row"/>
<field name="number_hours" type="measure"/>
</graph>
</field>
</record>
<record id="planning_slot_report_view_search" model="ir.ui.view">
<field name="name">planning.slot.report.search</field>
<field name="model">planning.slot.report.analysis</field>
<field name="arch" type="xml">
<search string="Planning Analysis">
<field name="employee_id" filter_domain="[('employee_id', 'ilike', self)]"/>
<field name="role_id" filter_domain="[('role_id', 'ilike', self)]"/>
<field name="entry_date"/>
<filter string="Date" name="year" date="entry_date"/>
<separator/>
<group expand="1" string="Group By">
<filter string="Employee" name="resource_employee" context="{'group_by':'employee_id'}"/>
<filter string="Role" name="resource_role" context="{'group_by':'role_id'}"/>
<filter string="Company" name="resource_company" context="{'group_by':'company_id'}"
groups="base.group_multi_company"/>
<separator/>
<filter string="Date" name="date_month" context="{'group_by':'entry_date:month'}"/>
</group>
</search>
</field>
</record>
<record id="planning_action_analysis" model="ir.actions.act_window">
<field name="name">Shifts Analysis</field>
<field name="res_model">planning.slot.report.analysis</field>
<field name="view_mode">pivot,graph</field>
</record>
<!-- Filter for graph view -->
<record id="planning_filter_by_employee" model="ir.filters">
<field name="name">Hours per Employee</field>
<field name="model_id">planning.slot.report.analysis</field>
<field name="user_id" eval="False"/>
<field name="is_default" eval="True"/>
<field name="context">{
'pivot_measures': ['number_hours'],
'pivot_column_groupby': ['entry_date:month'],
'pivot_row_groupby': ['employee_id'],
'graph_measures': ['number_hours'],
'graph_column_groupby': ['entry_date:month'],
'graph_row_groupby': ['employee_id']
}</field>
<field name="action_id" ref="planning_action_analysis"/>
</record>
</odoo>

View File

@ -0,0 +1,11 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_planning_slot_user,planning.slot.user,planning.model_planning_slot,planning.group_planning_user,1,0,0,0
access_planning_slot_manager,planning.slot.manager,planning.model_planning_slot,planning.group_planning_manager,1,1,1,1
access_planning_role_user,planning.role.user,planning.model_planning_role,planning.group_planning_user,1,0,0,0
access_planning_role_manager,planning.role.manager,planning.model_planning_role,planning.group_planning_manager,1,1,1,1
access_planning_recurrency_user,planning.recurrency.user,planning.model_planning_recurrency,planning.group_planning_user,1,0,0,0
access_planning_recurrency_manager,planning.recurrency.manager,planning.model_planning_recurrency,planning.group_planning_manager,1,1,1,1
access_planning_planning,access_planning_planning,model_planning_planning,planning.group_planning_manager,1,1,1,1
access_planning_slot_template_user,planning.slot.template.user,planning.model_planning_slot_template,planning.group_planning_user,1,0,0,0
access_planning_slot_template_manager,planning.slot.template.manager,planning.model_planning_slot_template,planning.group_planning_manager,1,1,1,1
access_planning_slot_report_analysis,access_planning_slot_report_analysis,model_planning_slot_report_analysis,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_planning_slot_user planning.slot.user planning.model_planning_slot planning.group_planning_user 1 0 0 0
3 access_planning_slot_manager planning.slot.manager planning.model_planning_slot planning.group_planning_manager 1 1 1 1
4 access_planning_role_user planning.role.user planning.model_planning_role planning.group_planning_user 1 0 0 0
5 access_planning_role_manager planning.role.manager planning.model_planning_role planning.group_planning_manager 1 1 1 1
6 access_planning_recurrency_user planning.recurrency.user planning.model_planning_recurrency planning.group_planning_user 1 0 0 0
7 access_planning_recurrency_manager planning.recurrency.manager planning.model_planning_recurrency planning.group_planning_manager 1 1 1 1
8 access_planning_planning access_planning_planning model_planning_planning planning.group_planning_manager 1 1 1 1
9 access_planning_slot_template_user planning.slot.template.user planning.model_planning_slot_template planning.group_planning_user 1 0 0 0
10 access_planning_slot_template_manager planning.slot.template.manager planning.model_planning_slot_template planning.group_planning_manager 1 1 1 1
11 access_planning_slot_report_analysis access_planning_slot_report_analysis model_planning_slot_report_analysis base.group_user 1 1 1 1

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<record id="group_planning_user" model="res.groups">
<field name="name">User</field>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
<field name="category_id" ref="base.module_category_human_resources_planning"/>
</record>
<record id="group_planning_manager" model="res.groups">
<field name="name">Manager</field>
<field name="category_id" ref="base.module_category_human_resources_planning"/>
<field name="implied_ids" eval="[(4, ref('group_planning_user'))]"/>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
<record id="group_planning_show_percentage" model="res.groups">
<field name="name">Show Allocated Percentage</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
</data>
<data noupdate="1">
<record id="planning_rule_user_modify" model="ir.rule">
<field name="name">Planning: user can modify their own shifts</field>
<field name="model_id" ref="planning.model_planning_slot"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="False"/>
<field name="groups" eval="[(4,ref('group_planning_user'))]"/>
</record>
<record id="planning_rule_user_is_published" model="ir.rule">
<field name="name">Planning: user can only see published shifts</field>
<field name="model_id" ref="planning.model_planning_slot"/>
<field name="domain_force">[('is_published', '=', True)]</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
<field name="groups" eval="[(4,ref('group_planning_user'))]"/>
</record>
<record id="planning_rule_manager" model="ir.rule">
<field name="name">Planning: manager can create/update/delete all planning entries</field>
<field name="model_id" ref="planning.model_planning_slot"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="groups" eval="[(4,ref('group_planning_manager'))]"/>
</record>
<record id="planning_slot_rule_multi_company" model="ir.rule">
<field name="name">Planning Shift multi-company</field>
<field name="model_id" ref="planning.model_planning_slot"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="global" eval="True"/>
</record>
<record id="planning_recurrency_rule_multi_company" model="ir.rule">
<field name="name">Planning Recurrence multi-company</field>
<field name="model_id" ref="planning.model_planning_recurrency"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="global" eval="True"/>
</record>
<record id="planning_planning_rule_multi_company" model="ir.rule">
<field name="name">Planning Planning multi-company</field>
<field name="model_id" ref="planning.model_planning_planning"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="global" eval="True"/>
</record>
<record id="planning_planning_rule_multi_company" model="ir.rule">
<field name="name">Planning Analysis multi-company</field>
<field name="model_id" ref="planning.model_planning_slot_report_analysis"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="global" eval="True"/>
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70"><defs><path id="a" d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z"/><linearGradient id="c" x1="100%" x2="0%" y1="0%" y2="100%"><stop offset="0%" stop-color="#269396"/><stop offset="100%" stop-color="#218689"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><g><path fill="url(#c)" d="M0 0H70V70H0z"/><path fill="#FFF" fill-opacity=".383" d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z"/></g><path fill="#000" fill-opacity=".383" d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z"/><path fill="#393939" d="M3.935 69c-1.3 0-4-.4-4-3.5L0 27.1l13.33-9.8 36.4 5.9L40 28.7l5.6.1 11.8 4.4-3.825 7.42 3.025 9.807L44.235 69h-40.3z" opacity=".32"/><g fill="#000" fill-rule="nonzero" opacity=".3"><path d="M48.1 40.6c-5.3 0-9.6 4.3-9.6 9.7 0 5.4 4.3 9.7 9.6 9.7s9.6-4.3 9.6-9.7c0-5.4-4.3-9.7-9.6-9.7zm2 10.8c0 .1-.1.3-.2.4l-3.4 2.5c-.2.2-.5.1-.7-.1l-1.1-1.5c-.2-.2-.1-.5.1-.7l2.5-1.8v-5.4c0-.3.2-.5.5-.5h1.9c.3 0 .5.2.5.5v6.6h-.1zm-.7-25.5H14c-.6 0-1.2-.5-1.2-1.2v-3.8c0-.6.5-1.2 1.2-1.2h35.4c.6 0 1.2.5 1.2 1.2v3.8c-.1.6-.6 1.2-1.2 1.2zm6.8 11.2H20.8c-.6 0-1.2-.5-1.2-1.2v-3.8c0-.6.5-1.2 1.2-1.2h35.4c.6 0 1.2.5 1.2 1.2v3.8c-.1.7-.6 1.2-1.2 1.2zM35.473 47.3c0-1.9.41-3.6 1.227-5.1H15.518c-.49 0-.818.5-.818 1v4.1c0 .6.409 1 .818 1h20.037c-.082-.4-.082-.7-.082-1z"/><path d="M42.5 43.2v-1.3l-26.8 1.3c-.6 0-1 .5-1 1v4.1c0 .6.5 1 1 1h24.5c0-2.4.9-4.5 2.3-6.1z"/></g><path fill="#FFF" fill-rule="nonzero" d="M49.4 23.2H14c-.6 0-1.2-.5-1.2-1.2v-3.8c0-.6.5-1.2 1.2-1.2h35.4c.6 0 1.2.5 1.2 1.2V22c-.1.7-.6 1.2-1.2 1.2zm6.8 11.2H20.8c-.6 0-1.2-.5-1.2-1.2v-3.8c0-.6.5-1.2 1.2-1.2h35.4c.6 0 1.2.5 1.2 1.2v3.8c-.1.7-.6 1.2-1.2 1.2zm-18.5 6.1H15.527c-.496 0-.827.5-.827 1v4.1c0 .6.414 1 .827 1h20.27c0-2.3.745-4.5 1.903-6.1zm10.4-3.3c-5.3 0-9.6 4.3-9.6 9.7 0 5.4 4.3 9.7 9.6 9.7s9.6-4.3 9.6-9.7c0-5.4-4.3-9.7-9.6-9.7zm2 10.8c0 .1-.1.3-.2.4l-3.4 2.5c-.2.2-.5.1-.7-.1l-1.1-1.5c-.2-.2-.1-.5.1-.7l2.5-1.8v-5.4c0-.3.2-.5.5-.5h1.9c.3 0 .5.2.5.5V48h-.1z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,109 @@
odoo.define('web.FieldColorPicker', function (require) {
"use strict";
var basic_fields = require('web.basic_fields');
var FieldInteger = basic_fields.FieldInteger;
var field_registry = require('web.field_registry');
var core = require('web.core');
var QWeb = core.qweb;
var FieldColorPicker = FieldInteger.extend({
/**
* Prepares the rendering, since we are based on an input but not using it
* setting tagName after parent init force the widget to not render an input
*
* @override
*/
init: function () {
this._super.apply(this, arguments);
this.tagName = 'div';
},
/**
* Render the widget when it is edited.
*
* @override
*/
_renderEdit: function () {
this.$el.html(QWeb.render('web.ColorPicker'));
this._setupColorPicker();
this._highlightSelectedColor();
},
/**
* Render the widget when it is NOT edited.
*
* @override
*/
_renderReadonly: function () {
this.$el.html(QWeb.render('web.ColorPickerReadonly', {active_color: this.value,}));
this.$el.on('click', 'a', function(ev){ ev.preventDefault(); });
},
/**
* Render the kanban colors inside first ul element.
* This is the same template as in KanbanRecord.
*
* <a> elements click are bound to _onColorChanged
*
*/
_setupColorPicker: function () {
var $colorpicker = this.$('ul');
if (!$colorpicker.length) {
return;
}
$colorpicker.html(QWeb.render('KanbanColorPicker'));
$colorpicker.on('click', 'a', this._onColorChanged.bind(this));
},
/**
* Returns the widget value.
* Since NumericField is based on an input, but we don't use it,
* we override this function to use the internal value of the widget.
*
*
* @override
* @returns {string}
*/
_getValue: function (){
return this.value;
},
/**
* Listener in edit mode for click on a color.
* The actual color can be found in the data-color
* attribute of the target element.
*
* We re-render the widget after the update because
* the selected color has changed and it should
* be reflected in the ui.
*
* @param ev
*/
_onColorChanged: function(ev) {
ev.preventDefault();
var color = null;
if(ev.currentTarget && ev.currentTarget.dataset && ev.currentTarget.dataset.color){
color = ev.currentTarget.dataset.color;
}
if(color){
this.value = color;
this._onChange();
this._renderEdit();
}
},
/**
* Helper to modify the active color's style
* while in edit mode.
*
*/
_highlightSelectedColor: function(){
try{
$(this.$('li')[parseInt(this.value)]).css('border', '2px solid teal');
} catch(err) {
}
},
});
field_registry.add('color_picker', FieldColorPicker);
return FieldColorPicker;
});

View File

@ -0,0 +1,142 @@
odoo.define('planning.PlanningGanttController', function (require) {
'use strict';
var GanttController = require('web_gantt.GanttController');
var core = require('web.core');
var _t = core._t;
var confirmDialog = require('web.Dialog').confirm;
var dialogs = require('web.view_dialogs');
var QWeb = core.qweb;
var PlanningGanttController = GanttController.extend({
events: _.extend({}, GanttController.prototype.events, {
'click .o_gantt_button_copy_previous_week': '_onCopyWeekClicked',
'click .o_gantt_button_send_all': '_onSendAllClicked',
}),
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* @override
* @param {jQueryElement} $node to which the buttons will be appended
*/
renderButtons: function ($node) {
if ($node) {
var state = this.model.get();
this.$buttons = $(QWeb.render('PlanningGanttView.buttons', {
groupedBy: state.groupedBy,
widget: this,
SCALES: this.SCALES,
activateScale: state.scale,
allowedScales: this.allowedScales,
activeActions: this.activeActions,
}));
this.$buttons.appendTo($node);
}
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Opens dialog to add/edit/view a record
* Override required to execute the reload of the gantt view when an action is performed on a
* single record.
*
* @private
* @param {integer|undefined} resID
* @param {Object|undefined} context
*/
_openDialog: function (resID, context) {
var self = this;
var record = resID ? _.findWhere(this.model.get().records, {id: resID,}) : {};
var title = resID ? record.display_name : _t("Open");
var dialog = new dialogs.FormViewDialog(this, {
title: _.str.sprintf(title),
res_model: this.modelName,
view_id: this.dialogViews[0][0],
res_id: resID,
readonly: !this.is_action_enabled('edit'),
deletable: this.is_action_enabled('edit') && resID,
context: _.extend({}, this.context, context),
on_saved: this.reload.bind(this, {}),
on_remove: this._onDialogRemove.bind(this, resID),
});
dialog.on('closed', this, function(ev){
// we reload as record can be created or modified (sent, unpublished, ...)
self.reload();
});
return dialog.open();
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {MouseEvent} ev
*/
_onCopyWeekClicked: function (ev) {
ev.preventDefault();
var state = this.model.get();
var self = this;
self._rpc({
model: self.modelName,
method: 'action_copy_previous_week',
args: [
self.model.convertToServerTime(state.startDate),
],
context: _.extend({}, self.context || {}),
})
.then(function(){
self.reload();
});
},
/**
* @private
* @param {MouseEvent} ev
*/
_onSendAllClicked: function (ev) {
ev.preventDefault();
var self = this;
var state = this.model.get();
var additional_context = _.extend({}, this.context, {
'default_start_datetime': this.model.convertToServerTime(state.startDate),
'default_end_datetime': this.model.convertToServerTime(state.stopDate),
'scale': state.scale,
'active_domain': this.model.domain,
'active_ids': this.model.get().records
});
return this.do_action('planning.planning_send_action', {
additional_context: additional_context,
on_close: function () {
self.reload();
}
});
},
/**
* @private
* @override
* @param {MouseEvent} ev
*/
_onScaleClicked: function (ev) {
this._super.apply(this, arguments);
var $button = $(ev.currentTarget);
var scale = $button.data('value');
if (scale !== 'week') {
this.$('.o_gantt_button_copy_previous_week').hide();
} else {
this.$('.o_gantt_button_copy_previous_week').show();
}
},
});
return PlanningGanttController;
});

View File

@ -0,0 +1,34 @@
odoo.define('planning.PlanningGanttModel', function (require) {
"use strict";
var GanttModel = require('web_gantt.GanttModel');
var _t = require('web.core')._t;
var PlanningGanttModel = GanttModel.extend({
/**
* @private
* @override
* @returns {Object[]}
*/
_generateRows: function (params) {
var rows = this._super(params);
// is the data grouped by?
if(params.groupedBy && params.groupedBy.length){
// in the last row is the grouped by field is null
if(rows && rows.length && rows[rows.length - 1] && !rows[rows.length - 1].resId){
// then make it the first one
rows.unshift(rows.pop());
}
}
// rename 'Undefined Employee' into 'Open Shifts'
_.each(rows, function(row){
if(row.groupedByField === 'employee_id' && !row.resId){
row.name = _t('Open Shifts');
}
});
return rows;
},
});
return PlanningGanttModel;
});

View File

@ -0,0 +1,21 @@
odoo.define('planning.PlanningGanttView', function (require) {
'use strict';
var GanttView = require('web_gantt.GanttView');
var PlanningGanttController = require('planning.PlanningGanttController');
var PlanningGanttModel = require('planning.PlanningGanttModel');
var view_registry = require('web.view_registry');
var PlanningGanttView = GanttView.extend({
config: _.extend({}, GanttView.prototype.config, {
Controller: PlanningGanttController,
Model: PlanningGanttModel,
}),
});
view_registry.add('planning_gantt', PlanningGanttView);
return PlanningGanttView;
});

View File

@ -0,0 +1,41 @@
odoo.define('planning.tour', function (require) {
"use strict";
var core = require('web.core');
var tour = require('web_tour.tour');
var _t = core._t;
tour.register('planning_tour', {
'skip_enabled': true,
}, [{
trigger: '.o_app[data-menu-xmlid="planning.planning_menu_root"]',
content: _t("Let's start managing your employees' schedule!"),
position: 'bottom',
}, {
trigger: ".o_menu_header_lvl_1[data-menu-xmlid='planning.planning_menu_schedule']",
content: _t("Use this menu to visualize and schedule shifts"),
position: "bottom"
}, {
trigger: ".o_menu_entry_lvl_2[data-menu-xmlid='planning.planning_menu_schedule_by_employee']",
content: _t("Use this menu to visualize and schedule shifts"),
position: "bottom"
}, {
trigger: ".o_gantt_button_add",
content: _t("Create your first shift by clicking on Add. Alternatively, you can use the (+) on the Gantt view."),
position: "bottom",
}, {
trigger: "button[special='save']",
content: _t("Save this shift as a template to reuse it, or make it recurrent. This will greatly ease your encoding process."),
position: "bottom",
}, {
trigger: ".o_gantt_button_send_all",
content: _t("Send the schedule to your employees once it is ready."),
position: "right",
},{
trigger: "button[name='action_send']",
content: _t("Send the schedule and mark the shifts as published. Congratulations!"),
position: "right",
},
]);
});

View File

@ -0,0 +1,53 @@
.o_field_color_picker {
display: flex;
ul {
display: flex;
flex-wrap: wrap;
width: 100%;
@include o-kanban-colorpicker;
@include o-kanban-record-color;
max-width: unset;
> li {
border: 2px solid white;
box-shadow: 0 0 0 1px gray('300');
}
}
}
.o_field_color_picker_preview {
@include o-kanban-record-color;
> li {
display: inline-block;
margin: $o-kanban-inner-hmargin $o-kanban-inner-hmargin 0 0;
border: 1px solid white;
box-shadow: 0 0 0 1px gray('300');
> a {
display: block;
&::after {
content: "";
display: block;
width: 20px;
height: 15px;
}
}
// No Color
a.oe_kanban_color_0 {
position: relative;
&::before {
content: "";
@include o-position-absolute(-2px, $left: 10px);
display: block;
width: 1px;
height: 20px;
transform: rotate(45deg);
background-color: red;
}
&::after {
background-color: white;
}
}
}
}

View File

@ -0,0 +1,26 @@
.o_planning_calendar_container {
#calendar_employee {
flex-direction: column;
max-width: 100%;
height: auto;
}
.fc-day-grid-event {
margin: 5px 2px 0;
padding: 5px 10px;
}
.fc-day-grid-event .fc-content {
white-space: normal;
}
}
.o_planning_calendar_open_shifts {
.close {
outline: none !important;
}
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="web.ColorPicker">
<div class="o_field_color_picker">
<ul>
</ul>
</div>
</t>
<t t-name="web.ColorPickerReadonly">
<div class="o_field_color_picker_preview">
<li><a role="menuitem" href="#" t-att-data-color="active_color" t-att-class="'oe_kanban_color_'+active_color" title="No color" aria-label="No color"/></li>
</div>
</t>
</templates>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<div t-name="PlanningGanttView.buttons">
<button t-if="widget.is_action_enabled('create')" class="o_gantt_button_add btn btn-primary mr-3" title="Add record">
Add
</button>
<div class="d-inline-block mr-3">
<button class="o_gantt_button_prev btn btn-primary" title="Previous">
<span class="fa fa-arrow-left"/>
</button>
<button class="o_gantt_button_today btn btn-primary">
Today
</button>
<button class="o_gantt_button_next btn btn-primary" title="Next">
<span class="fa fa-arrow-right"/>
</button>
</div>
<button t-foreach="allowedScales" t-as="scale" t-attf-class="o_gantt_button_scale btn btn-secondary #{activateScale == scale ? 'active' : ''}" type="button" t-att-data-value="scale">
<t t-esc="SCALES[scale].string"/>
</button>
<div class="btn-group">
<button class="o_gantt_button_expand_rows btn btn-secondary" title="Expand rows">
<i class="fa fa-expand"/>
</button>
<button class="o_gantt_button_collapse_rows btn btn-secondary" title="Collapse rows">
<i class="fa fa-compress"/>
</button>
</div>
<button t-if="activeActions.create &amp;&amp; activateScale == 'week'" class="o_gantt_button_copy_previous_week btn btn-secondary mr-3" title="Copy previous week">
Copy previous week
</button>
<button t-if="activeActions.edit" class="o_gantt_button_send_all btn btn-primary" title="Send schedule">
Send schedule
</button>
</div>
<t t-name="PlanningGanttView.Row" t-extend="GanttView.Row">
<t t-jquery=".o_gantt_row_title" t-operation="prepend">
<t t-if="widget.groupedByField == 'employee_id'">
<img t-att-src="'/web/image?model=hr.employee&amp;field=image_128&amp;id='+widget.resId+'&amp;unique='" style="height: 30px; width: 30px;" class="o_object_fit_cover rounded-circle"/>
</t>
</t>
</t>
</templates>

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details
from . import test_recurrency
from . import test_publication
# from . import test_period_duplication

48
planning/tests/common.py Normal file
View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details
from contextlib import contextmanager
from odoo import fields
from odoo.tests.common import SavepointCase
class TestCommonPlanning(SavepointCase):
@contextmanager
def _patch_now(self, datetime_str):
datetime_now_old = getattr(fields.Datetime, 'now')
datetime_today_old = getattr(fields.Datetime, 'today')
def new_now():
return fields.Datetime.from_string(datetime_str)
def new_today():
return fields.Datetime.from_string(datetime_str).replace(hour=0, minute=0, second=0)
try:
setattr(fields.Datetime, 'now', new_now)
setattr(fields.Datetime, 'today', new_today)
yield
finally:
# back
setattr(fields.Datetime, 'now', datetime_now_old)
setattr(fields.Datetime, 'today', datetime_today_old)
def get_by_employee(self, employee):
return self.env['planning.slot'].search([('employee_id', '=', employee.id)])
@classmethod
def setUpEmployees(cls):
cls.employee_joseph = cls.env['hr.employee'].create({
'name': 'joseph',
'work_email': 'joseph@a.be',
'tz': 'UTC'
})
cls.employee_bert = cls.env['hr.employee'].create({
'name': 'bert',
'work_email': 'bert@a.be',
'tz': 'UTC'
})

View File

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details
from datetime import datetime, date
from odoo.addons.planning.tests.common import TestCommonPlanning
class TestRecurrencySlotGeneration(TestCommonPlanning):
@classmethod
def setUpClass(cls):
super(TestRecurrencySlotGeneration, cls).setUpClass()
cls.setUpEmployees()
cls.recurrency = cls.env['planning.recurrency'].create({
'repeat_interval': 1,
'repeat_unit': 'week',
})
cls.env['planning.slot'].create({
'employee_id': cls.employee_bert.id,
'start_datetime': datetime(2019, 6, 2, 8, 0),
'end_datetime': datetime(2019, 6, 2, 17, 0),
})
cls.env['planning.slot'].create({
'employee_id': cls.employee_bert.id,
'start_datetime': datetime(2019, 6, 4, 8, 0),
'end_datetime': datetime(2019, 6, 5, 17, 0),
})
cls.env['planning.slot'].create({
'employee_id': cls.employee_bert.id,
'start_datetime': datetime(2019, 6, 3, 8, 0),
'end_datetime': datetime(2019, 6, 3, 17, 0),
'recurrency_id': cls.recurrency.id
})
def test_dont_duplicate_recurring_slots(self):
"""Original week : 6/2/2019 -> 6/8/2019
Destination week : 6/9/2019 -> 6/15/2019
slots:
6/2/2019 08:00 -> 6/2/2019 17:00
6/4/2019 08:00 -> 6/5/2019 17:00
6/3/2019 08:00 -> 6/3/2019 17:00 --> this one should be recurrent therefore not duplicated
"""
employee = self.employee_bert
self.assertEqual(len(self.get_by_employee(employee)), 3)
self.env['planning.slot'].action_copy_previous_week(date(2019, 6, 9))
self.assertEqual(len(self.get_by_employee(employee)), 5, 'duplicate has only duplicated slots that fit entirely in the period')
duplicated_slots = self.env['planning.slot'].search([
('employee_id', '=', employee.id),
('start_datetime', '>', datetime(2019, 6, 9, 0, 0)),
('end_datetime', '<', datetime(2019, 6, 15, 23, 59)),
])
self.assertEqual(len(duplicated_slots), 2, 'duplicate has only duplicated slots that fit entirely in the period')

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details
from datetime import datetime
from .common import TestCommonPlanning
class TestPlanningPublishing(TestCommonPlanning):
@classmethod
def setUpClass(cls):
super(TestPlanningPublishing, cls).setUpClass()
cls.setUpEmployees()
# employee without work email
cls.employee_dirk_no_mail = cls.env['hr.employee'].create({
'user_id': False,
'name': 'Dirk',
'work_email': False,
'tz': 'UTC'
})
values = {
'employee_id': cls.employee_joseph.id,
'allocated_hours': 8,
'start_datetime': datetime(2019, 6, 6, 8, 0, 0),
'end_datetime': datetime(2019, 6, 6, 17, 0, 0)
}
cls.shift = cls.env['planning.slot'].create(values)
def test_planning_publication(self):
self.shift.write({
'allocated_hours': 10
})
Mails = self.env['mail.mail']
before_mails = Mails.search([])
self.shift.action_send()
self.assertTrue(self.shift.is_published, 'planning is is_published when we call its action_send')
shift_mails = set(Mails.search([])) ^ set(before_mails)
self.assertEqual(len(shift_mails), 1, 'only one mail is created when publishing planning')
def test_sending_planning_do_not_create_mail_if_employee_has_no_email(self):
self.shift.write({'employee_id': self.employee_dirk_no_mail.id})
self.assertFalse(self.employee_dirk_no_mail.work_email) # if no work_email
Mails = self.env['mail.mail']
before_mails = Mails.search([])
self.shift.action_send()
shift_mails = set(Mails.search([])) ^ set(before_mails)
self.assertEqual(len(shift_mails), 0, 'no mail should be sent when the employee has no work email')

View File

@ -0,0 +1,409 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details
from datetime import datetime, date
from .common import TestCommonPlanning
import unittest
from odoo.exceptions import UserError
class TestRecurrencySlotGeneration(TestCommonPlanning):
@classmethod
def setUpClass(cls):
super(TestRecurrencySlotGeneration, cls).setUpClass()
cls.setUpEmployees()
def configure_recurrency_span(self, span_qty):
self.env.company.write({
'planning_generation_interval': span_qty,
})
# ---------------------------------------------------------
# Repeat "until" mode
# ---------------------------------------------------------
def test_repeat_until(self):
""" Normal case: Test slots get repeated at the right time with company limit
company_span: 2 weeks
first run:
now : 6/27/2019
initial_start : 6/27/2019
repeat_end : 7/11/2019 now + 2 weeks
generated slots:
6/27/2019
7/4/2019
NOT 7/11/2019 because it hits the soft limit
1st cron
now : 7/11/2019 2 weeks later
last generated start : 7/4/2019
repeat_end : 7/25/2019 now + 2 weeks
generated_slots:
7/11/2019
7/18/2019
NOT 7/25/2019 because it hits the soft limit
"""
with self._patch_now('2019-06-27 08:00:00'):
self.configure_recurrency_span(1)
self.assertFalse(self.get_by_employee(self.employee_joseph))
# repeat once, since repeat span is two week and there's no repeat until, we should have 2 slot
# because we hit the 'soft_limit'
slot = self.env['planning.slot'].create({
'start_datetime': datetime(2019, 6, 27, 8, 0, 0),
'end_datetime': datetime(2019, 6, 27, 17, 0, 0),
'employee_id': self.employee_joseph.id,
'repeat': True,
'repeat_type': 'until',
'repeat_until': datetime(2022, 6, 27, 17, 0, 0),
'repeat_interval': 1,
})
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 5, 'initial run should have created 2 slots')
# TODO JEM: check same employee, attached to same reccurrence, same role, ...
# now run cron two weeks later, should yield two more slots
with self._patch_now('2019-07-11 08:00:00'):
self.env['planning.recurrency']._cron_schedule_next()
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 7, 'first cron run should have generated 2 more forecasts')
def test_repeat_until_no_repeat(self):
"""create a recurrency with repeat until set which is less than next cron span, should
stop repeating upon creation
company_span: 2 weeks
first run:
now : 6/27/2019
initial_start : 6/27/2019
repeat_end : 6/29/2019 recurrency's repeat_until
generated slots:
6/27/2019
NOT 7/4/2019 because it hits the recurrency's repeat_until
"""
with self._patch_now('2019-06-27 08:00:00'):
self.configure_recurrency_span(1)
self.assertFalse(self.get_by_employee(self.employee_joseph))
slot = self.env['planning.slot'].create({
'start_datetime': datetime(2019, 6, 27, 8, 0, 0),
'end_datetime': datetime(2019, 6, 27, 17, 0, 0),
'employee_id': self.employee_joseph.id,
'repeat': True,
'repeat_type': 'until',
'repeat_interval': 1,
'repeat_until': datetime(2019, 6, 29, 8, 0, 0),
})
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 1, 'first run should only have created 1 slot since repeat until is set at 1 week')
def test_repeat_until_cron_idempotent(self):
"""Create a recurrency with repeat_until set, it allows a full first run, but not on next cron
first run:
now : 6/27/2019
initial_start : 6/27/2019
repeat_end : 7/11/2019 recurrency's repeat_until
generated slots:
6/27/2019
7/4/2019
NOT 7/11/2019 because it hits the recurrency's repeat_until
first cron:
now: 7/12/2019
last generated start: 7/4/2019
repeat_end: 7/11/2019 still recurrency's repeat_until
generated slots:
NOT 7/11/2019 because it still hits the repeat end
"""
with self._patch_now('2019-06-27 08:00:00'):
self.configure_recurrency_span(1)
self.assertFalse(self.get_by_employee(self.employee_joseph))
# repeat until is big enough for the first pass to generate all 2 slots
slot = self.env['planning.slot'].create({
'start_datetime': datetime(2019, 6, 27, 8, 0, 0),
'end_datetime': datetime(2019, 6, 27, 17, 0, 0),
'employee_id': self.employee_joseph.id,
'repeat': True,
'repeat_type': 'until',
'repeat_interval': 1,
'repeat_until': datetime(2019, 7, 11, 8, 0, 0),
})
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 2, 'initial run should have generated 2 slots')
# run the cron, since last generated slot almost hits the repeat until, there won't be more. still two left
self.env['planning.recurrency']._cron_schedule_next()
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 2, 'runing the cron right after do not generate new slots because of repeat until')
def test_repeat_until_cron_generation(self):
"""Generate a recurrence with repeat_until that allow first run, then first cron, but shouldn't
keep generating slots on the second
first run:
now : 8/31/2019
initial_start : 9/1/2019
repeat_end : forever
generated slots:
9/1/2019
9/8/2019
9/15/2019
9/22/2019
9/29/2019
first cron:
now: 9/14/2019 two weeks later
repeat_end: forever
generated slots:
9/1/2019
9/8/2019
9/15/2019
9/22/2019
9/29/2019
10/6/2019
10/13/2019
second cron:
now: 9/16/2019 two days later
last generated start: 10/13/2019
repeat_end: forever
generated slots:
NOT 10/20/2019 because all recurring slots are already generated in the company interval
"""
with self._patch_now('2019-08-31 08:00:00'):
self.configure_recurrency_span(1)
self.assertFalse(self.get_by_employee(self.employee_joseph))
# first run, two slots generated
slot = self.env['planning.slot'].create({
'start_datetime': datetime(2019, 9, 1, 8, 0, 0),
'end_datetime': datetime(2019, 9, 1, 17, 0, 0),
'employee_id': self.employee_joseph.id,
'repeat': True,
'repeat_type': 'forever',
'repeat_interval': 1,
'repeat_until': False,
})
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 5, 'first run should have geenrated 2 slots')
# run the cron, since last generated slot do not hit the repeat until, there will be 2 more
with self._patch_now('2019-09-14 08:00:00'):
self.env['planning.recurrency']._cron_schedule_next()
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 7, 'first cron should have generated 2 more slot')
# run the cron again, since last generated slot do hit the repeat until, there won't be more
with self._patch_now('2019-09-16 08:00:00'):
self.env['planning.recurrency']._cron_schedule_next()
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 7, 'second cron has not generated any foreasts because of repeat until')
def test_repeat_until_long_limit(self):
"""Since the recurrency cron is meant to run every week, make sure generation works accordingly when
the company's repeat span is much larger
first run:
now : 6/1/2019
initial_start : 6/1/2019
repeat_end : 12/1/2019 initial_start + 6 months
generated slots:
6/1/2019
...
11/30/2019 (27 items)
first cron:
now: 6/8/2019
last generated start 11/30/2019
repeat_end 12/8/2019
generated slots:
12/7/2019
only one slot generated: since we are one week later, repeat_end is only one week later and slots are generated every week.
So there is just enough room for one.
This ensure slots are always generated up to x time in advance with x being the company's repeat span
"""
with self._patch_now('2019-06-01 08:00:00'):
self.configure_recurrency_span(6)
self.assertFalse(self.get_by_employee(self.employee_joseph))
slot = self.env['planning.slot'].create({
'start_datetime': datetime(2019, 6, 1, 8, 0, 0),
'end_datetime': datetime(2019, 6, 1, 17, 0, 0),
'employee_id': self.employee_joseph.id,
'repeat': True,
'repeat_type': 'until',
'repeat_interval': 1,
'repeat_until': datetime(2020, 7, 25, 8, 0, 0),
})
# over 6 month, we should have generated 27 slots
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 27, 'first run has generated 27 slots')
# one week later, always having the slots generated 6 months in advance means we
# have generated one more, which makes 28
with self._patch_now('2019-06-08 08:00:00'):
self.env['planning.recurrency']._cron_schedule_next()
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 28, 'second cron only has generated 1 more slot because of company span')
def test_repeat_forever(self):
""" Since the recurrency cron is meant to run every week, make sure generation works accordingly when
both the company's repeat span and the repeat interval are much larger
Company's span is 6 months and repeat_interval is 1 month
first run:
now : 6/1/2019
initial_start : 6/1/2019
repeat_end : 12/1/2019 initial_start + 6 months
generated slots:
6/1/2019
...
11/1/2019 (27 items)
first cron:
now: 6/8/2019
last generated start 11/30/2019
repeat_end 12/8/2019
generated slots:
12/1/2019
second cron:
now: 6/15/2019
last generated start 12/1/2019
repeat_end 12/15/2019
generated slots:
N/A (we are still 6 months in advance)
"""
with self._patch_now('2019-06-01 08:00:00'):
self.configure_recurrency_span(6)
self.assertFalse(self.get_by_employee(self.employee_joseph))
slot = self.env['planning.slot'].create({
'start_datetime': datetime(2019, 6, 1, 8, 0, 0),
'end_datetime': datetime(2019, 6, 1, 17, 0, 0),
'employee_id': self.employee_joseph.id,
'repeat': True,
'repeat_type': 'forever',
'repeat_interval': 1,
})
# over 6 month, we should have generated 6 slots
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 27, 'first run has generated 6 slots')
# one week later, always having the slots generated 6 months in advance means we
# have generated one more, which makes 7
with self._patch_now('2019-06-08 08:00:00'):
self.env['planning.recurrency']._cron_schedule_next()
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 28, 'first cron generated one more slot')
# again one week later, we are now up-to-date so there should still be 7 slots
with self._patch_now('2019-06-15 08:00:00'):
self.env['planning.recurrency']._cron_schedule_next()
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 29, 'second run has not generated any forecats because of company span')
@unittest.skip
def kkktest_slot_remove_all(self):
with self._patch_now('2019-06-01 08:00:00'):
self.configure_recurrency_span(6)
initial_start_dt = datetime(2019, 6, 1, 8, 0, 0)
initial_end_dt = datetime(2019, 6, 1, 17, 0, 0)
slot_values = {
'employee_id': self.employee_joseph.id,
}
recurrency = self.env['planning.recurrency'].create({
'repeat_interval': 1,
})
self.assertFalse(self.get_by_employee(self.employee_joseph))
recurrency.create_slot(initial_start_dt, initial_end_dt, slot_values)
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 27, 'first run has generated 27 slots')
recurrency.action_remove_all()
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 0, 'calling remove after on any slot from the recurrency remove all slots linked to the recurrency')
# ---------------------------------------------------------
# Recurring Slot Misc
# ---------------------------------------------------------
def test_recurring_slot_company(self):
with self._patch_now('2019-06-01 08:00:00'):
initial_company = self.env['res.company'].create({'name': 'original'})
initial_company.write({
'planning_generation_interval': 2,
})
with self.assertRaises(UserError, msg="The employee should be in the same company as the shift"), self.cr.savepoint():
slot1 = self.env['planning.slot'].create({
'start_datetime': datetime(2019, 6, 1, 8, 0, 0),
'end_datetime': datetime(2019, 6, 1, 17, 0, 0),
'employee_id': self.employee_bert.id,
'repeat': True,
'repeat_type': 'forever',
'repeat_interval': 1,
'company_id': initial_company.id,
})
# put the employee in the second company
self.employee_bert.write({'company_id': initial_company.id})
slot1 = self.env['planning.slot'].create({
'start_datetime': datetime(2019, 6, 1, 8, 0, 0),
'end_datetime': datetime(2019, 6, 1, 17, 0, 0),
'employee_id': self.employee_bert.id,
'repeat': True,
'repeat_type': 'forever',
'repeat_interval': 1,
'company_id': initial_company.id,
})
other_company = self.env['res.company'].create({'name': 'other'})
other_company.write({
'planning_generation_interval': 1,
})
self.employee_joseph.write({'company_id': other_company.id})
slot2 = self.env['planning.slot'].create({
'start_datetime': datetime(2019, 6, 1, 8, 0, 0),
'end_datetime': datetime(2019, 6, 1, 17, 0, 0),
'employee_id': self.employee_joseph.id,
'repeat': True,
'repeat_type': 'forever',
'repeat_interval': 1,
'company_id': other_company.id,
})
# initial company's recurrency should have created 9 slots since it's span is two month
# other company's recurrency should have create 5 slots since it's span is one month
self.assertEqual(len(self.get_by_employee(self.employee_bert)), 9, 'initial company\'s span is two month, so 9 slots')
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 5, 'other company\'s span is one month, so only 5 slots')
self.assertEqual(slot1.company_id, slot1.recurrency_id.company_id, "Recurrence and slots (1) must have the same company")
self.assertEqual(slot1.recurrency_id.company_id, slot1.recurrency_id.slot_ids.mapped('company_id'), "All slots in the same recurrence (1) must have the same company")
self.assertEqual(slot2.company_id, slot2.recurrency_id.company_id, "Recurrence and slots (2) must have the same company")
self.assertEqual(slot2.recurrency_id.company_id, slot2.recurrency_id.slot_ids.mapped('company_id'), "All slots in the same recurrence (1) must have the same company")
def test_slot_detach_if_some_fields_change(self):
with self._patch_now('2019-06-27 08:00:00'):
self.configure_recurrency_span(1)
self.assertFalse(self.get_by_employee(self.employee_joseph))
slot = self.env['planning.slot'].create({
'start_datetime': datetime(2019, 6, 27, 8, 0, 0),
'end_datetime': datetime(2019, 6, 27, 17, 0, 0),
'employee_id': self.employee_joseph.id,
'repeat': True,
'repeat_type': 'until',
'repeat_until': datetime(2019, 9, 27, 17, 0, 0), # 3 months
'repeat_interval': 1,
})
recurrence = slot.recurrency_id
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), 5)
self.assertEqual(len(self.get_by_employee(self.employee_joseph)), len(recurrence.slot_ids), 'the recurrency has generated 5 slots')
self.get_by_employee(self.employee_joseph)[0].write({'employee_id': self.employee_bert.id})
self.assertEqual(len(recurrence.slot_ids), 4, 'writing on the slot detach it from the recurrency')
def test_empty_recurrency(self):
""" Check empty recurrency is removed by cron """
with self._patch_now('2020-06-27 08:00:00'):
self.env['planning.recurrency'].create({
'repeat_interval': 1,
'repeat_type': 'forever',
'repeat_until': False,
'last_generated_end_datetime': datetime(2019, 6, 27, 8, 0, 0)
})
self.assertEqual(len(self.env['planning.recurrency'].search([])), 1)
self.env['planning.recurrency']._cron_schedule_next()
self.assertFalse(len(self.env['planning.recurrency'].search([])), 'cron with no slot gets deleted (there is no original slot to copy from)')

23
planning/views/assets.xml Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_backend" name="planning_backend_assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="planning/static/src/js/planning_gantt_controller.js"/>
<script type="text/javascript" src="planning/static/src/js/planning_gantt_model.js"/>
<script type="text/javascript" src="planning/static/src/js/planning_gantt_view.js"/>
<link rel="stylesheet" type="text/scss" href="/planning/static/src/scss/field_colorpicker.scss"/>
<script type="text/javascript" src="planning/static/src/js/field_colorpicker.js" />
<script type="text/javascript" src="planning/static/src/js/tours/planning.js"></script>
</xpath>
</template>
<template id="assets_frontend_planning" name="Planning Own Assets Frontend" inherit_id="web.assets_common" primary="True">
<xpath expr="//link[last()]" position="after">
<link rel="stylesheet" href="/web/static/lib/fullcalendar/css/fullcalendar.css"/>
<link rel="stylesheet" type="text/scss" href="/planning/static/src/scss/planning_calendar_report.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/form_view.scss"/>
<script type="text/javascript" src="/web/static/lib/fullcalendar/js/fullcalendar.js"/>
</xpath>
</template>
</odoo>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_employee_view_form_inherit" model="ir.ui.view">
<field name="name">hr.employee.view.form.planning</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@id='o_work_employee_main']" position="inside">
<group name="planning" string="Planning">
<field name="planning_role_id"/>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="planning_slot_template_view_form" model="ir.ui.view">
<field name="name">planning.slot.template.form</field>
<field name="model">planning.slot.template</field>
<field name="arch" type="xml">
<form string="Shift Template Form">
<sheet>
<group>
<group>
<field name="role_id"/>
</group>
<group>
<field name="start_time" widget="float_time"/>
<field name="duration" widget="float_time"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="planning_slot_template_view_tree" model="ir.ui.view">
<field name="name">planning.slot.template.tree</field>
<field name="model">planning.slot.template</field>
<field name="arch" type="xml">
<tree string="Shift Template List" editable="top">
<field name="role_id"/>
<field name="start_time" widget="float_time"/>
<field name="duration" widget="float_time"/>
</tree>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,176 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="period_report_template">
<t t-call="web.frontend_layout">
<t t-set="head">
<t t-call="planning.planning_shift_notification" />
<t t-call-assets="web.assets_frontend" t-js="false"/>
<t t-call-assets="planning.assets_frontend_planning" t-js="false"/>
<t t-call-assets="planning.assets_frontend_planning" t-css="false"/>
<script>
// Initialization of fullcalendar
$(document).ready(function() {
var employee_slots_fc_data = <t t-raw="json.dumps(employee_slots_fullcalendar_data)" />;
var employee_token = "<t t-esc="employee.employee_token" />";
var planning_token = "<t t-esc="planning_planning_id.access_token" />";
// Hide the unassign footer by default
document.getElementById("dismiss_shift").style.display = "none";
var eventFunction = function(calEvent) {
$(".modal-title").text(calEvent.title);
$(".modal-header").css("background-color", calEvent.color);
$("#start").text(moment(calEvent.start).format("YYYY-MM-DD hh:mm A"));
$("#stop").text(moment(calEvent.end).format("YYYY-MM-DD hh:mm A"));
$("#alloc_hours").text(calEvent.alloc_hours);
$("#allow_self_unassign").text(calEvent.allow_self_unassign);
if (calEvent.note) {
$("#note").text(calEvent.note);
}
else {
$("#note").text("");
}
if (calEvent.allow_self_unassign) {
document.getElementById("dismiss_shift").style.display = "block";
}
else {
document.getElementById("dismiss_shift").style.display = "none";
}
$("#modal_action_dismiss_shift").attr("action", "/planning/" + planning_token + "/" + employee_token + "/unassign/" + calEvent.slot_id);
$("#fc-slot-onclick-modal").modal("show");
}
$("#calendar_employee").fullCalendar({
// Settings
defaultView: 'month',
defaultDate: moment.min(employee_slots_fc_data.map(item => moment(item.start))),
titleFormat: 'MMMM YYYY',
timeFormat: 'hh:mm A',
displayEventEnd: true,
height: 'auto',
eventTextColor: 'white',
eventOverlap: true,
header: {
left: 'title',
center: false,
right: 'today prev,next'
},
eventRender: function (event, element, view) {
$(element).css('font-weight', 'bold');
},
// Data
events: employee_slots_fc_data,
// Event Functions
eventClick: eventFunction
});
});
</script>
</t>
<body>
<!-- fullcalendar object's container -->
<div class="container o_planning_calendar_container">
<h1 align="center" class="m-3">Planning: <t t-esc="employee.name"/></h1>
<div id="calendar_employee" class="o_calendar_container">
<div class="o_calendar_view" >
<div class="o_calendar_widget" />
</div>
</div>
</div>
<div class="container o_planning_calendar_open_shifts d-print-none mt2">
<!-- open shift's container -->
<t t-if="open_slots_ids">
<div class="container">
<h1 align="center" class="m-3">Open shifts available</h1>
<div>
<table class="table table-borderless">
<thead>
<tr>
<th class="text-left">From</th>
<th class="text-left">To</th>
<th class="text-left">Role</th>
<th class="text-left">Note</th>
</tr>
</thead>
<tbody>
<t t-foreach="open_slots_ids" t-as="shift">
<tr>
<td class="align-middle"><t t-esc="format_datetime(shift.start_datetime, 'medium')"/></td>
<td class="align-middle"><t t-esc="format_datetime(shift.end_datetime, 'medium')"/></td>
<td class="align-middle"><t t-esc="shift.role_id.name"/></td>
<td class="align-middle"><t t-esc="shift.name"/></td>
<td>
<t t-if="not shift.employee_id">
<div class="text-center float-right">
<form t-attf-action="/planning/#{planning_planning_id.access_token}/#{employee.employee_token}/assign/#{shift.id}" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<button type="submit" class="btn btn-primary">ASSIGN ME THIS SHIFT</button>
</form>
</div>
</t>
</td>
</tr>
</t>
</tbody>
</table>
</div>
</div>
</t>
<!-- fullcalendar event onclick Modal -->
<div class="modal fade" id="fc-slot-onclick-modal" role="dialog">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0">
<div class="modal-header modal-header-primary py-3 border-0 rounded-top text-light">
<h5 class="modal-title"/>
<button type="button" class="close text-light shadow-none" data-dismiss="modal">
<span aria-label="Close">&amp;times;</span>
</button>
</div>
<div class="modal-body">
<dl class="row mt-0 mb-0">
<dt class="col-sm-4">Start Date:</dt>
<dd class="col-sm-8" id="start"/>
<dt class="col-sm-4">Stop Date:</dt>
<dd class="col-sm-8" id="stop"/>
<dt class="col-sm-4">Allocated Hours:</dt>
<dd class="col-sm-8" id="alloc_hours"/>
<dt class="col-sm-4">Note:</dt>
<dd class="col-sm-8" id="note"/>
<div class="d-none" t-esc="shift_id" id="shift_uid"/>
</dl>
</div>
<div id="dismiss_shift" class="modal-footer">
<form id="modal_action_dismiss_shift" t-attf-action="/planning" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<button type="submit" class="btn btn-outline-danger">I am unavailable</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</t>
</template>
<!-- notification sub template -->
<template id="planning_shift_notification" name="Shift notification">
<t t-if="notification_text">
<div class="alert alert-success" role="alert">
<t t-if="message_slug == 'assign'">
This shift is now assigned to you.
</t>
<t t-if="message_slug == 'unassign'">
This shift is not assigned to you anymore.
</t>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
&amp;times;
</button>
</div>
</t>
</template>
</odoo>

View File

@ -0,0 +1,429 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- planning.slot views -->
<record id="planning_view_tree" model="ir.ui.view">
<field name="name">planning.slot.tree</field>
<field name="model">planning.slot</field>
<field name="arch" type="xml">
<tree string="Shift List">
<field name="employee_id"/>
<field name="role_id"/>
<field name="start_datetime"/>
<field name="end_datetime"/>
<field name="allocated_hours" widget="float_time"/>
<field name="allocated_percentage" groups="planning.group_planning_show_percentage"/>
<field name="company_id" groups="base.group_multi_company" optional="show"/>
</tree>
</field>
</record>
<record id="planning_view_form" model="ir.ui.view">
<field name="name">planning.slot.form</field>
<field name="model">planning.slot</field>
<field name="arch" type="xml">
<form>
<div role="alert" class="alert-warning p-3 text-center" attrs="{'invisible': [('overlap_slot_count', '=', 0)]}">
<button name="action_see_overlaping_slots" type="object" class="btn-link">
<field name="overlap_slot_count"/> other shift(s)
</button>
<span class="align-middle">for this employee at the same time.</span>
</div>
<sheet string="Shift">
<field name="is_assigned_to_me" invisible="1"/>
<field name="allow_self_unassign" invisible="1"/>
<group attrs="{'invisible': ['|', ('template_autocomplete_ids', '=', []), ('template_creation', '=', True)]}" groups="planning.group_planning_manager">
<field name="template_id" class="text-break flex-wrap"
domain="[['id', 'in', template_autocomplete_ids]]"
widget="radio" options="{'horizontal': true}"
/>
</group>
<field name="template_autocomplete_ids" invisible="1"/>
<field name="recurrency_id" invisible='1'/>
<group>
<group>
<field name="employee_id"/>
<field name="role_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group>
<field name="start_datetime" string="Start date"/>
<field name="end_datetime" string="End date"/>
<field name="allocated_hours" widget="float_time"/>
<field name="allocated_percentage" groups="planning.group_planning_show_percentage"/>
</group>
</group>
<group>
<field name="repeat" groups="planning.group_planning_manager"/>
<label for="repeat_interval" string="Repeat Every" attrs="{'invisible': [('repeat', '=', False)]}"/>
<div class="o_row" attrs="{'invisible': [('repeat', '=', False)]}">
<field name="repeat_interval" class="oe_inline" nolabel="1"/>
<span>week(s)</span>
<field name="repeat_type" class="oe_inline" nolabel="1" attrs="{'required': [('repeat', '=', True)]}"/>
<field name="repeat_until" class="oe_inline" attrs="{'invisible': [('repeat_type', '!=', 'until')], 'required': [('repeat_type', '=', 'until')]}" nolabel="1"/>
</div>
</group>
<group>
<field name="name"/>
</group>
<label for="template_creation" class="float-sm-right mr-5" groups="planning.group_planning_manager"/>
<field name="template_creation" class="float-sm-right" groups="planning.group_planning_manager"/>
</sheet>
</form>
</field>
</record>
<record id="planning_view_form_in_gantt" model="ir.ui.view">
<field name="name">planning.slot.form.gantt</field>
<field name="model">planning.slot</field>
<field name="inherit_id" ref="planning_view_form"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<xpath expr="//sheet" position="after">
<footer>
<field name="publication_warning" invisible="1"/>
<button string="Save" special="save" class="btn btn-primary" close="1" groups="planning.group_planning_manager"/>
<!--<button string="Publish &amp; Send" type="object" name="action_send" class="btn btn-primary" close="1" attrs="{'invisible': [('employee_id', '=', False)]}" groups="planning.group_planning_manager"/>-->
<button string="Publish" type="object" name="action_publish" class="btn btn-primary" close="1" groups="planning.group_planning_manager"/>
<button name="unlink" string="Delete" type="object" class="btn-secondary" close="1" attrs="{'invisible': [('id', '=', False)]}" confirm="Are you sure you want to do delete this shift?" groups="planning.group_planning_manager"/>
<button string="Discard" special="cancel" class="btn-secondary" close="1" fullscreen="True" groups="planning.group_planning_manager"/>
<button name="action_self_assign" class="btn btn-primary float-right" type="object" string="I take it" attrs="{'invisible': [('employee_id', '!=', False)]}" close="1"/>
<button name="action_self_unassign" class="btn btn-secondary float-right" type="object" string="I am unavailable" attrs="{'invisible': ['|', ('is_assigned_to_me', '=', False), ('allow_self_unassign', '=', False)]}" close="1"/>
</footer>
</xpath>
</field>
</record>
<record id="planning_view_form_quickcreate" model="ir.ui.view">
<field name="name">planning.slot.form.quickcreate</field>
<field name="model">planning.slot</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="employee_id"/>
<field name="role_id"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="name"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="planning_view_search" model="ir.ui.view">
<field name="name">planning.slot.search</field>
<field name="model">planning.slot</field>
<field name="arch" type="xml">
<search>
<field name="role_id"/>
<field name="employee_id"/>
<filter name="open_shifts" string="Open Shifts" domain="[('employee_id', '=', False)]"/>
<filter name="my_shifts" string="My Shifts" domain="[('user_id', '=', uid)]"/>
<separator/>
<filter name="future" string="Future" domain="[('start_datetime', '>=', time.strftime('%%Y-%%m-%%d 00:00:00'))]" />
<filter name="past" string="Past" domain="[('start_datetime', '&lt;=', time.strftime('%%Y-%%m-%%d 23:59:59'))]" />
<group string="Group By">
<filter name="group_by_employee" string="Employee" context="{'group_by': 'employee_id'}"/>
<filter name="group_by_role" string="Role" context="{'group_by': 'role_id'}"/>
<filter name="group_by_start_datetime" string="Start Date" context="{'group_by':'start_datetime:day'}"/>
</group>
</search>
</field>
</record>
<record id="planning_view_calendar" model="ir.ui.view">
<field name="name">planning.slot.calendar</field>
<field name="model">planning.slot</field>
<field name="arch" type="xml">
<calendar string="Planning" date_start="start_datetime" date_stop="end_datetime" color="role_id" form_view_id="%(planning_view_form_quickcreate)d" event_open_popup="true" quick_add="False">
<field name="name"/>
<field name="employee_id" avatar_field="image_128"/>
<field name="role_id"/>
</calendar>
</field>
</record>
<record id="planning_view_gantt" model="ir.ui.view">
<field name="name">planning.slot.gantt</field>
<field name="model">planning.slot</field>
<field name="arch" type="xml">
<gantt
js_class="planning_gantt"
form_view_id="%(planning_view_form_in_gantt)d"
date_start="start_datetime"
date_stop="end_datetime"
default_group_by="employee_id"
default_scale="week"
color="color"
plan="false"
scales="day,week,month,year"
precision="{'day': 'hour:full', 'week': 'day:full', 'month': 'day:full', 'year': 'day:full'}"
decoration-info="not is_published"
decoration-warning="publication_warning and is_published"
decoration-danger="overlap_slot_count > 0"
display_unavailability="1"
thumbnails="{'employee_id': 'image_128'}">
<field name="allocated_hours"/>
<field name="recurrency_id" />
<field name="is_published"/>
<field name="publication_warning"/>
<field name="employee_id"/>
<field name="overlap_slot_count"/>
<field name="allocated_percentage"/>
<templates>
<div t-name="gantt-popover" class="container-fluid">
<div class="row no-gutters">
<div class="col">
<ul class="pl-1 mb-0">
<li><strong>Start Date: </strong> <t t-esc="userTimezoneStartDate.format('YYYY-MM-DD hh:mm:ss A')"/></li>
<li><strong>Stop Date: </strong> <t t-esc="userTimezoneStopDate.format('YYYY-MM-DD hh:mm:ss A')"/></li>
<li id="allocated_hours"><strong>Allocated Hours: </strong> <t t-esc="'' + Math.floor(allocated_hours) + ':' + ((allocated_hours % 1) * 60 >= 10 ? (allocated_hours % 1) * 60 : '0'+(allocated_hours % 1) * 60)"/></li>
</ul>
<t t-if="publication_warning">
<br/>
<span>Some changes were made since this shift was published</span>
</t>
</div>
</div>
</div>
</templates>
</gantt>
</field>
</record>
<record id="planning_view_gantt_inherit" model="ir.ui.view">
<field name="name">planning.slot.gantt.inherit</field>
<field name="model">planning.slot</field>
<field name="inherit_id" ref="planning.planning_view_gantt"/>
<field name="groups_id" eval="[(4,ref('planning.group_planning_show_percentage'))]"/>
<field name="arch" type="xml">
<xpath expr="//li[@id='allocated_hours']" position="after">
<t t-if="allocated_percentage != 100">
<li><strong>Allocated Time (%): </strong> <t t-esc="Math.round(allocated_percentage)"/></li>
</t>
</xpath>
</field>
</record>
<record id="planning_view_pivot" model="ir.ui.view">
<field name="name">planning.slot.pivot</field>
<field name="model">planning.slot</field>
<field name="arch" type="xml">
<pivot string="Planning Analysis">
<field name="start_datetime" interval="week" type="col"/>
<field name="allocated_hours" type="measure"/>
<field name="allocated_percentage" type="measure"/>
</pivot>
</field>
</record>
<record id="planning_view_graph" model="ir.ui.view">
<field name="name">planning.slot.graph</field>
<field name="model">planning.slot</field>
<field name="arch" type="xml">
<graph string="Planning Analysis" type="bar">
<field name="role_id" type="row"/>
<field name="employee_id" type="col"/>
<field name="allocated_hours" type="measure"/>
<field name="allocated_percentage" type="measure"/>
</graph>
</field>
</record>
<!-- planning.role views -->
<record id="planning_role_view_tree" model="ir.ui.view">
<field name="name">planning.role.tree</field>
<field name="model">planning.role</field>
<field name="arch" type="xml">
<tree string="Planning Role List" editable="top">
<field name="name"/>
<field name="color" widget="color_picker"/>
</tree>
</field>
</record>
<record id="planning_role_view_form" model="ir.ui.view">
<field name="name">planning.role.form</field>
<field name="model">planning.role</field>
<field name="arch" type="xml">
<form string="Planning Role">
<field name="name"/>
<field name="color" widget="color_picker"/>
</form>
</field>
</record>
<!--
Actions
-->
<record id="planning_action_my_calendar" model="ir.actions.act_window">
<field name="name">My Planning</field>
<field name="res_model">planning.slot</field>
<field name="view_mode">calendar,gantt,tree,form</field>
<field name="context">{'search_default_my_shifts': 1}</field>
</record>
<record id="planning_action_my_gantt" model="ir.actions.act_window">
<field name="name">My Planning</field>
<field name="res_model">planning.slot</field>
<field name="view_mode">gantt,calendar,tree,form</field>
<field name="context">{'search_default_my_shifts': 1}</field>
</record>
<record id="planning_action_open_shift" model="ir.actions.act_window">
<field name="name">Open Shifts</field>
<field name="res_model">planning.slot</field>
<field name="view_mode">gantt,calendar,tree,form</field>
<field name="context">{'search_default_open_shifts': 1, 'search_default_my_shifts': 1, 'default_employee_id': False}</field>
</record>
<record id="planning_action_open_shift_view_gantt" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">gantt</field>
<field name="view_id" ref="planning_view_gantt"/>
<field name="act_window_id" ref="planning_action_open_shift"/>
</record>
<record id="planning_action_schedule_by_employee" model="ir.actions.act_window">
<field name="name">Planning Schedule</field>
<field name="res_model">planning.slot</field>
<field name="view_mode">gantt,calendar,tree,form</field>
<field name="context">{'search_default_group_by_employee': 1, 'planning_expand_employee': 1}</field>
</record>
<record id="planning_action_schedule_by_employee_view_gantt" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">gantt</field>
<field name="view_id" ref="planning_view_gantt"/>
<field name="act_window_id" ref="planning_action_schedule_by_employee"/>
</record>
<record id="planning_action_schedule_by_role" model="ir.actions.act_window">
<field name="name">Planning Schedule</field>
<field name="res_model">planning.slot</field>
<field name="view_mode">gantt,calendar,tree,form</field>
<field name="context">{'search_default_group_by_role': 1, 'search_default_group_by_employee': 2, 'planning_expand_employee': 1}</field>
</record>
<record id="planning_action_schedule_by_role_view_gantt" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">gantt</field>
<field name="view_id" ref="planning_view_gantt"/>
<field name="act_window_id" ref="planning_action_schedule_by_role"/>
</record>
<record id="planning_action_settings" model="ir.actions.act_window">
<field name="name">Settings</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">inline</field>
<field name="context">{'module' : 'planning'}</field>
</record>
<record id="planning_action_roles" model="ir.actions.act_window">
<field name="name">Planning Roles</field>
<field name="res_model">planning.role</field>
<field name="view_mode">tree</field>
</record>
<record id="planning_action_shift_template" model="ir.actions.act_window">
<field name="name">Shift Templates</field>
<field name="res_model">planning.slot.template</field>
<field name="view_mode">tree</field>
</record>
<!--
Menus
-->
<menuitem
id="planning_menu_root"
name="Planning"
sequence="25"
groups="planning.group_planning_user"
web_icon="planning,static/description/icon.png"/>
<menuitem
id="planning_menu_my_planning"
name="My Planning"
sequence="10"
parent="planning_menu_root"/>
<menuitem
id="planning_menu_calendar"
name="Calendar"
sequence="10"
parent="planning_menu_my_planning"
action="planning_action_my_calendar"/>
<menuitem
id="planning_menu_schedule"
name="Schedule"
sequence="30"
parent="planning_menu_root"
groups="planning.group_planning_manager,planning.group_planning_user"/>
<menuitem
id="planning_menu_schedule_by_employee"
name="By Employee"
sequence="10"
parent="planning_menu_schedule"
action="planning_action_schedule_by_employee"/>
<menuitem
id="planning_menu_schedule_by_role"
name="By Role"
sequence="20"
parent="planning_menu_schedule"
action="planning_action_schedule_by_role"/>
<menuitem
id="planning_menu_reporting"
name="Reporting"
parent="planning_menu_root"
sequence="40"
groups="planning.group_planning_manager"/>
<menuitem
id="planning_menu_planning_analysis"
name="Planning Analysis"
action="planning_action_analysis"
sequence="10" parent="planning_menu_reporting"
groups="planning.group_planning_manager"/>
<menuitem
id="planning_menu_settings"
name="Configuration"
parent="planning_menu_root"
sequence="50"
groups="base.group_system"/>
<menuitem
id="planning_menu_settings_config"
name="Settings"
parent="planning_menu_settings"
sequence="10"
action="planning_action_settings"
groups="base.group_system"/>
<menuitem
id="planning_menu_settings_role"
name="Roles"
parent="planning_menu_settings"
sequence="20"
action="planning_action_roles"
groups="base.group_system"/>
<menuitem
id="planning_menu_settings_shift_template"
name="Shift Templates"
parent="planning_menu_settings"
sequence="30"
action="planning_action_shift_template"
groups="base.group_system"/>
</odoo>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.planning</field>
<field name="model">res.config.settings</field>
<field name="priority" eval="55"/>
<field name="inherit_id" ref="base.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[hasclass('settings')]" position="inside">
<div class="app_settings_block" data-string="Planning" string="Planning" data-key="planning" groups="planning.group_planning_manager">
<h2>Planning</h2>
<div class="row mt16 o_settings_container" name="planning">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="planning_allow_self_unassign"/>
</div>
<div class="o_setting_right_pane">
<label for="planning_allow_self_unassign"/>
<div class="text-muted">
Let employees unassign themselves from shifts
</div>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box" groups="base.group_no_one">
<div class="o_setting_right_pane">
<div class="text-muted" name="project_forecast_msg">
Schedule your employee shifts
</div>
<div class="content-group">
<div class="row mt16">
<label for="planning_generation_interval" class="col-4 col-lg-4 o_light_label" string="Rate of shift generation"/>
<field name="planning_generation_interval" class="oe_inline col-4 col-lg-2 mr-1"/>
</div>
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import planning_send

View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from odoo import models, fields
from odoo.osv import expression
class PlanningSend(models.TransientModel):
_name = 'planning.send'
_description = "Send Planning"
start_datetime = fields.Datetime("Start Date", required=True)
end_datetime = fields.Datetime("Stop Date", required=True)
include_unassigned = fields.Boolean("Includes Open shifts", default=True)
note = fields.Text("Extra Message", help="Additional message displayed in the email sent to employees")
company_id = fields.Many2one('res.company', "Company", required=True, default=lambda self: self.env.company)
_sql_constraints = [
('check_start_date_lower_stop_date', 'CHECK(end_datetime > start_datetime)', 'Planning end date should be greater than its start date'),
]
def action_send(self):
# create the planning
planning = self.env['planning.planning'].create({
'start_datetime': self.start_datetime,
'end_datetime': self.end_datetime,
'include_unassigned': self.include_unassigned,
'company_id': self.company_id.id,
})
return planning.send_planning(message=self.note)
def action_publish(self):
# get user tz here to accord start and end datetime ?
domain = [
('start_datetime', '>=', datetime.combine(fields.Date.from_string(self.start_datetime), datetime.min.time())),
('end_datetime', '<=', datetime.combine(fields.Date.from_string(self.end_datetime), datetime.max.time())),
('company_id', '=', self.company_id.id),
]
if not self.include_unassigned:
domain = expression.AND([domain, [('employee_id', '!=', False)]])
to_publish = self.env['planning.slot'].sudo().search(domain)
to_publish.write({
'is_published': True,
'publication_warning': False
})
return True

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="planning_send_view_form" model="ir.ui.view">
<field name="name">planning.send.form</field>
<field name="model">planning.send</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="start_datetime" readonly="1"/>
<field name="end_datetime" readonly="1"/>
</group>
<group>
<field name="include_unassigned"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<group>
<field name="note" nolabel="1" placeholder="Additional message"/>
</group>
</sheet>
<footer>
<button name="action_send" type="object" string="Publish &amp; Send" class='btn-primary'/>
<button name="action_publish" type="object" string="Publish" class='btn-primary'/>
<button name="discard" string="Discard" class='btn-secondary' special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="planning_send_action" model="ir.actions.act_window">
<field name="name">Send Planning Shifts</field>
<field name="res_model">planning.send</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from. import models

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': "Planning",
'summary': """Plan your resources on project tasks""",
'description': """
Schedule your teams across projects and estimate deadlines more accurately.
""",
'category': 'Operations/Project',
'version': '1.0',
'depends': ['project', 'planning'],
'data': [
'views/planning_views.xml',
'views/project_forecast_views.xml',
'views/project_views.xml',
'data/project_forecast_data.xml',
],
'application': False,
'license': 'OEEL-1',
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="ir_filter_forecast_time_by_month" model="ir.filters">
<field name="name">Time by month</field>
<field name="model_id">planning.slot</field>
<field name="domain">[]</field>
<field name="user_id" eval="False"/>
<field name="context">{'graph_mode': 'bar', 'graph_groupbys': ['start_datetime', 'employee_id'], 'group_by': ['start_datetime', 'employee_id'], 'graph_measure': 'allocated_percentage'}</field>
<field name="action_id" ref="project_forecast_action_analysis"/>
</record>
<!--
Since Forecast is installed, we want the users to see the percentage to enjoy all feature
-->
<record id="planning.group_planning_user" model="res.groups">
<field name="implied_ids" eval="[(4, ref('planning.group_planning_show_percentage'))]"/>
</record>
</odoo>

451
project_forecast/i18n/af.po Normal file
View File

@ -0,0 +1,451 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_forecast
#
# Translators:
# Martin Trigaux <mat@odoo.com>, 2017
# Andre de Kock <adekock11@gmail.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~18+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-09-20 11:33+0000\n"
"PO-Revision-Date: 2017-09-20 11:33+0000\n"
"Last-Translator: Andre de Kock <adekock11@gmail.com>, 2017\n"
"Language-Team: Afrikaans (https://www.transifex.com/odoo/teams/41243/af/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: af\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "% Time"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_view_kanban_inherit_project_forecast
msgid "<span class=\"o_label\">Forecast</span>"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
#: model:ir.ui.view,arch_db:project_forecast.task_view_form_inherit_project_forecast
msgid "<span>Forecast</span>"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:152
#, python-format
msgid ""
"A project must have a start date to use a forecast grid, found no start date"
" for {project.display_name}"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:170
#, python-format
msgid ""
"A project must have an end date to use a forecast grid, found no end date "
"for {project.display_name}"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_active
msgid "Active"
msgstr "Aktief"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_task_allow_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
msgid "Allow Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_project_allow_forecast
msgid "Allow forecast"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Archived"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_grid
msgid "Assign"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.action_project_forecast_assign
msgid "Assign user on a task"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_assign
msgid "Assign user on task"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.menu_project_forecast_grid_by_project
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_project
msgid "By Project"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.menu_project_forecast_grid_by_user
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_user
msgid "By User"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:28
#, python-format
msgid ""
"Can only be used for forecasts not spanning multiple months, found "
"%(forecast_count)d forecast(s) spanning across months in %(project_name)s"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_color
msgid "Color"
msgstr "Kleur"
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_assign
msgid "Create"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.task_view_form_inherit_project_forecast
msgid "Create a forecast"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_create_uid
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_create_uid
msgid "Created by"
msgstr "Geskep deur"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_create_date
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_create_date
msgid "Created on"
msgstr "Geskep op"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_display_name
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_display_name
msgid "Display Name"
msgstr "Vertoningsnaam"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_employee_id
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_employee_id
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Employee"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_end_date
msgid "End Date"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_exclude
msgid "Exclude"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:50
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_from_task
#: model:ir.ui.menu,name:project_forecast.project_forecast_menu
#: model:ir.ui.view,arch_db:project_forecast.project_view_kanban_inherit_project_forecast
#, python-format
msgid "Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_report_activities
#: model:ir.ui.menu,name:project_forecast.project_forecast_report_activities
msgid "Forecast Analysis"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.action_project_forecast_grid_by_project
msgid "Forecast By Project"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.action_project_forecast_grid_by_user
msgid "Forecast By User"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "Forecast Form"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_tree
msgid "Forecast List"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_project
msgid "Forecast by project"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_user
msgid "Forecast by user"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:97
#, python-format
msgid "Forecasted time must be positive"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_set_dates
msgid ""
"Forecasting on a project requires that the project\n"
" have start and end dates"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:148
#, python-format
msgid ""
"Forecasting over a project only supports monthly forecasts (got step {})"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Future"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_gantt
msgid "Gantt"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_pivot
msgid "Graph"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.menu_project_forecast_grid
msgid "Grid"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:186
#, python-format
msgid ""
"Grid adjustment for project forecasts only supports the 'start_date' columns"
" field and the 'resource_hours' cell field, got respectively "
"%(column_field)r and %(cell_field)r"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Group By"
msgstr "Groepeer deur"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_id
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_id
msgid "ID"
msgstr "ID"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast___last_update
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment___last_update
msgid "Last Modified on"
msgstr "Laas Gewysig op"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_write_uid
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_write_uid
msgid "Last Updated by"
msgstr "Laas Opgedateer deur"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_write_date
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_write_date
msgid "Last Updated on"
msgstr "Laas Opgedateer op"
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Month"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_grid
msgid "Monthly Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "My activities"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "My projects"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_name
msgid "Name"
msgstr "Naam"
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Past"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_forecast_time
msgid "Percentage of working time"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_resource_hours
msgid "Planned hours"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_project
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_project_id
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_project_id
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_grid
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Project"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_set_dates
msgid "Project Dates"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "Project Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
msgid "Project Forecast By Project"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Project Forecast By User"
msgstr ""
#. module: project_forecast
#: model:ir.actions.server,name:project_forecast.project_forecast_server_action_archive
msgid "Project Forecast: Archive/Restore forecasts"
msgstr ""
#. module: project_forecast
#: model:ir.actions.server,name:project_forecast.action_generate_forecast
msgid "Project: Generate Task Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_forecast_user_id
msgid "Related user name for the resource to manage its access."
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_start_date
msgid "Start Date"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_task
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_task_id
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_task_id
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Task"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_stage_id
msgid "Task stage"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_tag_ids
msgid "Task tags"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:109
#, python-format
msgid "The start-date must be lower than end-date."
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_project_allow_forecast
#: model:ir.model.fields,help:project_forecast.field_project_task_allow_forecast
msgid "This feature shows the Forecast link in the kanban view"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_user_id
msgid "User"
msgstr "Gebruiker"
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_set_dates
msgid "View Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Week"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Year"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:88
#, python-format
msgid "You cannot set a user with no working time."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:103
#, python-format
msgid "Your task is not in the selected project."
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_forecast
msgid "project.forecast"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_forecast_assignment
msgid "project.forecast.assignment"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:68
#, python-format
msgid "undefined"
msgstr ""

451
project_forecast/i18n/am.po Normal file
View File

@ -0,0 +1,451 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_forecast
#
# Translators:
# Martin Trigaux <mat@odoo.com>, 2017
# Kiros Haregewoine <kirosharegewoine@yahoo.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~18+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-09-20 11:33+0000\n"
"PO-Revision-Date: 2017-09-20 11:33+0000\n"
"Last-Translator: Kiros Haregewoine <kirosharegewoine@yahoo.com>, 2017\n"
"Language-Team: Amharic (https://www.transifex.com/odoo/teams/41243/am/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: am\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "% Time"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_view_kanban_inherit_project_forecast
msgid "<span class=\"o_label\">Forecast</span>"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
#: model:ir.ui.view,arch_db:project_forecast.task_view_form_inherit_project_forecast
msgid "<span>Forecast</span>"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:152
#, python-format
msgid ""
"A project must have a start date to use a forecast grid, found no start date"
" for {project.display_name}"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:170
#, python-format
msgid ""
"A project must have an end date to use a forecast grid, found no end date "
"for {project.display_name}"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_active
msgid "Active"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_task_allow_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
msgid "Allow Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_project_allow_forecast
msgid "Allow forecast"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Archived"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_grid
msgid "Assign"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.action_project_forecast_assign
msgid "Assign user on a task"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_assign
msgid "Assign user on task"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.menu_project_forecast_grid_by_project
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_project
msgid "By Project"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.menu_project_forecast_grid_by_user
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_user
msgid "By User"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:28
#, python-format
msgid ""
"Can only be used for forecasts not spanning multiple months, found "
"%(forecast_count)d forecast(s) spanning across months in %(project_name)s"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_color
msgid "Color"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_assign
msgid "Create"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.task_view_form_inherit_project_forecast
msgid "Create a forecast"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_create_uid
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_create_uid
msgid "Created by"
msgstr "ፈጣሪው"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_create_date
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_create_date
msgid "Created on"
msgstr "የተፈጠረበት"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_display_name
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_display_name
msgid "Display Name"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_employee_id
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_employee_id
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Employee"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_end_date
msgid "End Date"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_exclude
msgid "Exclude"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:50
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_from_task
#: model:ir.ui.menu,name:project_forecast.project_forecast_menu
#: model:ir.ui.view,arch_db:project_forecast.project_view_kanban_inherit_project_forecast
#, python-format
msgid "Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_report_activities
#: model:ir.ui.menu,name:project_forecast.project_forecast_report_activities
msgid "Forecast Analysis"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.action_project_forecast_grid_by_project
msgid "Forecast By Project"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.action_project_forecast_grid_by_user
msgid "Forecast By User"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "Forecast Form"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_tree
msgid "Forecast List"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_project
msgid "Forecast by project"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_user
msgid "Forecast by user"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:97
#, python-format
msgid "Forecasted time must be positive"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_set_dates
msgid ""
"Forecasting on a project requires that the project\n"
" have start and end dates"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:148
#, python-format
msgid ""
"Forecasting over a project only supports monthly forecasts (got step {})"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Future"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_gantt
msgid "Gantt"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_pivot
msgid "Graph"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.menu_project_forecast_grid
msgid "Grid"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:186
#, python-format
msgid ""
"Grid adjustment for project forecasts only supports the 'start_date' columns"
" field and the 'resource_hours' cell field, got respectively "
"%(column_field)r and %(cell_field)r"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Group By"
msgstr "በመደብ"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_id
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_id
msgid "ID"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast___last_update
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment___last_update
msgid "Last Modified on"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_write_uid
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_write_uid
msgid "Last Updated by"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_write_date
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_write_date
msgid "Last Updated on"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Month"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_grid
msgid "Monthly Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "My activities"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "My projects"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_name
msgid "Name"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Past"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_forecast_time
msgid "Percentage of working time"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_resource_hours
msgid "Planned hours"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_project
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_project_id
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_project_id
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_grid
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Project"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_set_dates
msgid "Project Dates"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "Project Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
msgid "Project Forecast By Project"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Project Forecast By User"
msgstr ""
#. module: project_forecast
#: model:ir.actions.server,name:project_forecast.project_forecast_server_action_archive
msgid "Project Forecast: Archive/Restore forecasts"
msgstr ""
#. module: project_forecast
#: model:ir.actions.server,name:project_forecast.action_generate_forecast
msgid "Project: Generate Task Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_forecast_user_id
msgid "Related user name for the resource to manage its access."
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_start_date
msgid "Start Date"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_task
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_assignment_task_id
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_task_id
#: model:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Task"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_stage_id
msgid "Task stage"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_tag_ids
msgid "Task tags"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:109
#, python-format
msgid "The start-date must be lower than end-date."
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_project_allow_forecast
#: model:ir.model.fields,help:project_forecast.field_project_task_allow_forecast
msgid "This feature shows the Forecast link in the kanban view"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast_user_id
msgid "User"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_set_dates
msgid "View Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Week"
msgstr ""
#. module: project_forecast
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
#: model:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Year"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:88
#, python-format
msgid "You cannot set a user with no working time."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:103
#, python-format
msgid "Your task is not in the selected project."
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_forecast
msgid "project.forecast"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_forecast_assignment
msgid "project.forecast.assignment"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:68
#, python-format
msgid "undefined"
msgstr ""

137
project_forecast/i18n/ar.po Normal file
View File

@ -0,0 +1,137 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_forecast
#
# Translators:
# Mustafa Rawi <mustafa@cubexco.com>, 2019
# Mohammed Albasha <m.albasha.ma@gmail.com>, 2019
# Shaima Safar <shaima.safar@open-inside.com>, 2019
# Martin Trigaux, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~12.5+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-27 09:19+0000\n"
"PO-Revision-Date: 2019-08-26 09:37+0000\n"
"Last-Translator: Martin Trigaux, 2019\n"
"Language-Team: Arabic (https://www.transifex.com/odoo/teams/41243/ar/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: ar\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_kanban_inherit_project_forecast
msgid "<span class=\"o_label\">Planning</span>"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
msgid "<span>Planning</span>"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_task__allow_forecast
msgid "Allow Planning"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_user
msgid "By Employee"
msgstr "حسب الموظف"
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.planning_menu_schedule_by_project
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_project
msgid "By Project"
msgstr "حسب المشروع"
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_project__allow_forecast
#: model:ir.model.fields,help:project_forecast.field_project_task__allow_forecast
msgid "Enable planning tasks on the project."
msgstr ""
#. module: project_forecast
#: model:ir.model.constraint,message:project_forecast.constraint_planning_slot_project_required_if_task
msgid "If the planning is linked to a task, the project must be set too."
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_from_project
#: model:ir.model.fields,field_description:project_forecast.field_project_project__allow_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_menu
msgid "Planning"
msgstr "التخطيط"
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_analysis
#: model:ir.ui.menu,name:project_forecast.project_forecast_report_activities
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_graph
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_pivot
msgid "Planning Analysis"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.planning_action_schedule_by_project
msgid "Planning Schedule"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_planning_slot
msgid "Planning Shift"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_user
msgid "Planning by Employee"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_project
msgid "Planning by project"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_project
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__project_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Project"
msgstr "المشروع"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_task
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__task_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Task"
msgstr "المهمة"
#. module: project_forecast
#: model:ir.filters,name:project_forecast.ir_filter_forecast_time_by_month
msgid "Time by month"
msgstr "الفترة بالشهر"
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a project containing plannings. You can either delete all "
"the project's forecasts and then delete the project or simply deactivate the"
" project."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a task containing plannings. You can either delete all the"
" task's plannings and then delete the task or simply deactivate the task."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:0
#, python-format
msgid "Your task is not in the selected project."
msgstr "مهمتك ليست في المشروع المحدد."

130
project_forecast/i18n/az.po Normal file
View File

@ -0,0 +1,130 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_forecast
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~12.5+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-27 09:19+0000\n"
"PO-Revision-Date: 2019-08-26 09:37+0000\n"
"Language-Team: Azerbaijani (https://www.transifex.com/odoo/teams/41243/az/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: az\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_kanban_inherit_project_forecast
msgid "<span class=\"o_label\">Planning</span>"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
msgid "<span>Planning</span>"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_task__allow_forecast
msgid "Allow Planning"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_user
msgid "By Employee"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.planning_menu_schedule_by_project
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_project
msgid "By Project"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_project__allow_forecast
#: model:ir.model.fields,help:project_forecast.field_project_task__allow_forecast
msgid "Enable planning tasks on the project."
msgstr ""
#. module: project_forecast
#: model:ir.model.constraint,message:project_forecast.constraint_planning_slot_project_required_if_task
msgid "If the planning is linked to a task, the project must be set too."
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_from_project
#: model:ir.model.fields,field_description:project_forecast.field_project_project__allow_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_menu
msgid "Planning"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_analysis
#: model:ir.ui.menu,name:project_forecast.project_forecast_report_activities
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_graph
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_pivot
msgid "Planning Analysis"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.planning_action_schedule_by_project
msgid "Planning Schedule"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_planning_slot
msgid "Planning Shift"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_user
msgid "Planning by Employee"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_project
msgid "Planning by project"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_project
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__project_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Project"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_task
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__task_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Task"
msgstr ""
#. module: project_forecast
#: model:ir.filters,name:project_forecast.ir_filter_forecast_time_by_month
msgid "Time by month"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a project containing plannings. You can either delete all "
"the project's forecasts and then delete the project or simply deactivate the"
" project."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a task containing plannings. You can either delete all the"
" task's plannings and then delete the task or simply deactivate the task."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:0
#, python-format
msgid "Your task is not in the selected project."
msgstr ""

View File

@ -0,0 +1,130 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_forecast
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~12.5+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-27 09:19+0000\n"
"PO-Revision-Date: 2019-08-26 09:37+0000\n"
"Language-Team: Azerbaijani (Azerbaijan) (https://www.transifex.com/odoo/teams/41243/az_AZ/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: az_AZ\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_kanban_inherit_project_forecast
msgid "<span class=\"o_label\">Planning</span>"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
msgid "<span>Planning</span>"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_task__allow_forecast
msgid "Allow Planning"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_user
msgid "By Employee"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.planning_menu_schedule_by_project
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_project
msgid "By Project"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_project__allow_forecast
#: model:ir.model.fields,help:project_forecast.field_project_task__allow_forecast
msgid "Enable planning tasks on the project."
msgstr ""
#. module: project_forecast
#: model:ir.model.constraint,message:project_forecast.constraint_planning_slot_project_required_if_task
msgid "If the planning is linked to a task, the project must be set too."
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_from_project
#: model:ir.model.fields,field_description:project_forecast.field_project_project__allow_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_menu
msgid "Planning"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_analysis
#: model:ir.ui.menu,name:project_forecast.project_forecast_report_activities
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_graph
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_pivot
msgid "Planning Analysis"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.planning_action_schedule_by_project
msgid "Planning Schedule"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_planning_slot
msgid "Planning Shift"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_user
msgid "Planning by Employee"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_project
msgid "Planning by project"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_project
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__project_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Project"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_task
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__task_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Task"
msgstr ""
#. module: project_forecast
#: model:ir.filters,name:project_forecast.ir_filter_forecast_time_by_month
msgid "Time by month"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a project containing plannings. You can either delete all "
"the project's forecasts and then delete the project or simply deactivate the"
" project."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a task containing plannings. You can either delete all the"
" task's plannings and then delete the task or simply deactivate the task."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:0
#, python-format
msgid "Your task is not in the selected project."
msgstr ""

137
project_forecast/i18n/bg.po Normal file
View File

@ -0,0 +1,137 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_forecast
#
# Translators:
# Martin Trigaux, 2020
# aleksandar ivanov, 2020
# Albena Mincheva <albena_vicheva@abv.bg>, 2020
# Maria Boyadjieva <marabo2000@gmail.com>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~12.5+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-27 09:19+0000\n"
"PO-Revision-Date: 2019-08-26 09:37+0000\n"
"Last-Translator: Maria Boyadjieva <marabo2000@gmail.com>, 2020\n"
"Language-Team: Bulgarian (https://www.transifex.com/odoo/teams/41243/bg/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: bg\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_kanban_inherit_project_forecast
msgid "<span class=\"o_label\">Planning</span>"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
msgid "<span>Planning</span>"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_task__allow_forecast
msgid "Allow Planning"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_user
msgid "By Employee"
msgstr "По служители"
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.planning_menu_schedule_by_project
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_project
msgid "By Project"
msgstr "По проект"
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_project__allow_forecast
#: model:ir.model.fields,help:project_forecast.field_project_task__allow_forecast
msgid "Enable planning tasks on the project."
msgstr ""
#. module: project_forecast
#: model:ir.model.constraint,message:project_forecast.constraint_planning_slot_project_required_if_task
msgid "If the planning is linked to a task, the project must be set too."
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_from_project
#: model:ir.model.fields,field_description:project_forecast.field_project_project__allow_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_menu
msgid "Planning"
msgstr "Планиране"
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_analysis
#: model:ir.ui.menu,name:project_forecast.project_forecast_report_activities
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_graph
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_pivot
msgid "Planning Analysis"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.planning_action_schedule_by_project
msgid "Planning Schedule"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_planning_slot
msgid "Planning Shift"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_user
msgid "Planning by Employee"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_project
msgid "Planning by project"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_project
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__project_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Project"
msgstr "Проект"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_task
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__task_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Task"
msgstr "Задача"
#. module: project_forecast
#: model:ir.filters,name:project_forecast.ir_filter_forecast_time_by_month
msgid "Time by month"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a project containing plannings. You can either delete all "
"the project's forecasts and then delete the project or simply deactivate the"
" project."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a task containing plannings. You can either delete all the"
" task's plannings and then delete the task or simply deactivate the task."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:0
#, python-format
msgid "Your task is not in the selected project."
msgstr "Вашата задача не е в избрания проект."

460
project_forecast/i18n/bs.po Normal file
View File

@ -0,0 +1,460 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_forecast
#
# Translators:
# Martin Trigaux, 2018
# Boško Stojaković <bluesoft83@gmail.com>, 2018
# Bole <bole@dajmi5.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~11.5+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-09-21 14:06+0000\n"
"PO-Revision-Date: 2018-09-21 14:06+0000\n"
"Last-Translator: Bole <bole@dajmi5.com>, 2018\n"
"Language-Team: Bosnian (https://www.transifex.com/odoo/teams/41243/bs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: bs\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "% Time"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:200
#, python-format
msgid "%s Task(s): %s"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_kanban_inherit_project_forecast
msgid "<span class=\"o_label\">Forecast</span>"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
msgid "<span>Forecast</span>"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__active
msgid "Active"
msgstr "Aktivan"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__resource_time
msgid "Allocated Time"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__time
msgid "Allocated time / Time span"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_task__allow_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
msgid "Allow Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_project__allow_forecast
msgid "Allow forecast"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Archived"
msgstr "Arhivirano"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "Assign To"
msgstr "Dodjeljen"
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_user
msgid "By Employee"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_project
msgid "By Project"
msgstr ""
#. module: project_forecast
#: selection:res.company,forecast_span:0
msgid "By day"
msgstr "Po danu"
#. module: project_forecast
#: selection:res.company,forecast_span:0
msgid "By month"
msgstr ""
#. module: project_forecast
#: selection:res.company,forecast_span:0
msgid "By week"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__color
msgid "Color"
msgstr "Boja"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_res_company
msgid "Companies"
msgstr "Kompanije"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__company_id
msgid "Company"
msgstr "Kompanija"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__create_uid
msgid "Created by"
msgstr "Kreirao"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__create_date
msgid "Created on"
msgstr "Kreirano"
#. module: project_forecast
#: selection:res.company,forecast_uom:0
msgid "Days"
msgstr "Dani"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__display_name
msgid "Display Name"
msgstr "Prikazani naziv"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__employee_id
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Employee"
msgstr "Zaposleni"
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_res_company__forecast_span
#: model:ir.model.fields,help:project_forecast.field_res_config_settings__forecast_span
msgid ""
"Encode your forecast in a table displayed by days, weeks or the whole year."
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_forecast__forecast_uom
#: model:ir.model.fields,help:project_forecast.field_res_company__forecast_uom
#: model:ir.model.fields,help:project_forecast.field_res_config_settings__forecast_uom
msgid "Encode your forecasts in hours or days."
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__end_date
msgid "End Date"
msgstr "Datum Završetka"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__exclude
msgid "Exclude"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_forecast__resource_time
msgid "Expressed in the Unit of Measure of the project company"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_from_project
#: model:ir.ui.menu,name:project_forecast.project_forecast_menu
msgid "Forecast"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:274
#, python-format
msgid "Forecast (in %s)"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_report_activities
#: model:ir.ui.menu,name:project_forecast.project_forecast_report_activities
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_graph
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_pivot
msgid "Forecast Analysis"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "Forecast Form"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_tree
msgid "Forecast List"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_user
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Forecast by employee"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_project
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
msgid "Forecast by project"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_grid_by_task
msgid "Forecast by task"
msgstr ""
#. module: project_forecast
#: sql_constraint:project.forecast:0
msgid "Forecast end date should be greater or equal to its start date"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:198
#, python-format
msgid ""
"Forecast should not overlap existing forecasts. To solve this, check the "
"project(s): %s."
msgstr ""
#. module: project_forecast
#: model:ir.actions.server,name:project_forecast.project_forecast_action_server_by_user
msgid "Forecast: view by employee"
msgstr ""
#. module: project_forecast
#: model:ir.actions.server,name:project_forecast.project_forecast_action_server_by_project
msgid "Forecast: view by project"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:164
#, python-format
msgid "Forecasted time must be positive"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Future"
msgstr "Buduće"
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:296
#, python-format
msgid ""
"Grid adjustment for project forecasts only supports the 'start_date' columns"
" field and the 'resource_time' cell field, got respectively %(column_field)r"
" and %(cell_field)r"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Group By"
msgstr "Grupiši po"
#. module: project_forecast
#: selection:res.company,forecast_uom:0
msgid "Hours"
msgstr "Sati"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__id
msgid "ID"
msgstr "ID"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast____last_update
msgid "Last Modified on"
msgstr "Zadnje mijenjano"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__write_uid
msgid "Last Updated by"
msgstr "Zadnji ažurirao"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__write_date
msgid "Last Updated on"
msgstr "Zadnje ažurirano"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_grid_by_task
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Month"
msgstr "Mjesec"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "My Activities"
msgstr "Moje aktivnosti"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "My Projects"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__name
msgid "Name"
msgstr "Naziv:"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Past"
msgstr "Prošlost"
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_forecast__time
msgid "Percentage of working time"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__resource_hours
msgid "Planned hours"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_project
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__project_id
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Project"
msgstr "Projekat"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "Project Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_forecast__user_id
msgid "Related user name for the resource to manage its access."
msgstr "Povezano korisničko ime za resurs da upravlja njegovim pristupom."
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__start_date
msgid "Start Date"
msgstr "Datum početka"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_task
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__task_id
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Task"
msgstr "Zadatak"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__stage_id
msgid "Task stage"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__tag_ids
msgid "Task tags"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:132
#, python-format
msgid ""
"The employee (%s) doesn't have a timezone, this might cause errors in the "
"time computation. It is configurable on the user linked to the employee"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_project__allow_forecast
#: model:ir.model.fields,help:project_forecast.field_project_task__allow_forecast
msgid "This feature shows the Forecast link in the kanban view"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_res_company__forecast_span
#: model:ir.model.fields,field_description:project_forecast.field_res_config_settings__forecast_span
msgid "Time Span"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__forecast_uom
#: model:ir.model.fields,field_description:project_forecast.field_res_company__forecast_uom
#: model:ir.model.fields,field_description:project_forecast.field_res_config_settings__forecast_uom
msgid "Time Unit"
msgstr ""
#. module: project_forecast
#: model:ir.filters,name:project_forecast.ir_filter_forecast_time_by_month
msgid "Time by month"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__user_id
msgid "User"
msgstr "Korisnik"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_grid_by_task
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Week"
msgstr "Sedmica"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_grid_by_task
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Year"
msgstr "Godina"
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:21
#, python-format
msgid ""
"You cannot delete a project containing forecasts. You can either delete all "
"the project's forecasts and then delete the project or simply deactivate the"
" project."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:43
#, python-format
msgid ""
"You cannot delete a task containing forecasts. You can either delete all the"
" task's forecasts and then delete the task or simply deactivate the task."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:170
#, python-format
msgid "Your task is not in the selected project."
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "day(s)"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:109
#, python-format
msgid "undefined"
msgstr ""

460
project_forecast/i18n/ca.po Normal file
View File

@ -0,0 +1,460 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_forecast
#
# Translators:
# Manel Fernandez <manelfera@outlook.com>, 2018
# Martin Trigaux, 2019
# Quim - eccit <quim@eccit.com>, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~12.2+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-20 14:08+0000\n"
"PO-Revision-Date: 2016-08-05 13:31+0000\n"
"Last-Translator: Quim - eccit <quim@eccit.com>, 2019\n"
"Language-Team: Catalan (https://www.transifex.com/odoo/teams/41243/ca/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: ca\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "% Time"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:202
#, python-format
msgid "%s Task(s): %s"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_kanban_inherit_project_forecast
msgid "<span class=\"o_label\">Forecast</span>"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
msgid "<span>Forecast</span>"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__active
msgid "Active"
msgstr "Actiu"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__resource_time
msgid "Allocated Time"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__time
msgid "Allocated time / Time span"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_task__allow_forecast
msgid "Allow Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_project__allow_forecast
msgid "Allow forecast"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Archived"
msgstr "Arxivat"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "Assign To"
msgstr "Assigna a"
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_user
msgid "By Employee"
msgstr "Per empleat"
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_project
msgid "By Project"
msgstr "Per Projecte"
#. module: project_forecast
#: selection:res.company,forecast_span:0
msgid "By day"
msgstr "Per dia"
#. module: project_forecast
#: selection:res.company,forecast_span:0
msgid "By month"
msgstr ""
#. module: project_forecast
#: selection:res.company,forecast_span:0
msgid "By week"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__color
msgid "Color"
msgstr "Color"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_res_company
msgid "Companies"
msgstr "Empreses"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__company_id
msgid "Company"
msgstr "Companyia"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__create_uid
msgid "Created by"
msgstr "Creat per"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__create_date
msgid "Created on"
msgstr "Creat el"
#. module: project_forecast
#: selection:res.company,forecast_uom:0
msgid "Days"
msgstr "Dies"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__display_name
msgid "Display Name"
msgstr "Mostrar Nom"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__employee_id
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Employee"
msgstr "Empleat"
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_project__allow_forecast
#: model:ir.model.fields,help:project_forecast.field_project_task__allow_forecast
msgid "Enable forecasting on the project."
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_res_company__forecast_span
#: model:ir.model.fields,help:project_forecast.field_res_config_settings__forecast_span
msgid ""
"Encode your forecast in a table displayed by days, weeks or the whole year."
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_forecast__forecast_uom
#: model:ir.model.fields,help:project_forecast.field_res_company__forecast_uom
#: model:ir.model.fields,help:project_forecast.field_res_config_settings__forecast_uom
msgid "Encode your forecasts in hours or days."
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__end_date
msgid "End Date"
msgstr "Data final"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__exclude
msgid "Exclude"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_forecast__resource_time
msgid "Expressed in the Unit of Measure of the project company"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_from_project
#: model:ir.ui.menu,name:project_forecast.project_forecast_menu
msgid "Forecast"
msgstr "Previsió"
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:276
#, python-format
msgid "Forecast (in %s)"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_report_activities
#: model:ir.ui.menu,name:project_forecast.project_forecast_report_activities
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_graph
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_pivot
msgid "Forecast Analysis"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "Forecast Form"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_tree
msgid "Forecast List"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_user
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Forecast by employee"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_project
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
msgid "Forecast by project"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_grid_by_task
msgid "Forecast by task"
msgstr ""
#. module: project_forecast
#: sql_constraint:project.forecast:0
msgid "Forecast end date should be greater or equal to its start date"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:200
#, python-format
msgid ""
"Forecast should not overlap existing forecasts. To solve this, check the "
"project(s): %s."
msgstr ""
#. module: project_forecast
#: model:ir.actions.server,name:project_forecast.project_forecast_action_server_by_user
msgid "Forecast: view by employee"
msgstr ""
#. module: project_forecast
#: model:ir.actions.server,name:project_forecast.project_forecast_action_server_by_project
msgid "Forecast: view by project"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:166
#, python-format
msgid "Forecasted time must be positive"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Future"
msgstr "Futur"
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:298
#, python-format
msgid ""
"Grid adjustment for project forecasts only supports the 'start_date' columns"
" field and the 'resource_time' cell field, got respectively %(column_field)r"
" and %(cell_field)r"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Group By"
msgstr "Agrupar per"
#. module: project_forecast
#: selection:res.company,forecast_uom:0
msgid "Hours"
msgstr "Hores"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__id
msgid "ID"
msgstr "ID"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast____last_update
msgid "Last Modified on"
msgstr "Última modificació el "
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__write_uid
msgid "Last Updated by"
msgstr "Última actualització per"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__write_date
msgid "Last Updated on"
msgstr "Última actualització el"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_grid_by_task
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Month"
msgstr "Mes"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "My Activities"
msgstr "Les meves activitats"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "My Projects"
msgstr "Els meus projectes"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__name
msgid "Name"
msgstr "Nom"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Past"
msgstr "Anterior"
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_forecast__time
msgid "Percentage of working time"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__resource_hours
msgid "Planned hours"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_project
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__project_id
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Project"
msgstr "Projecte"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "Project Forecast"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_forecast__user_id
msgid "Related user name for the resource to manage its access."
msgstr "Usuari relacionat amb el recurs per gestionar l'accés"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__start_date
msgid "Start Date"
msgstr "Data inicial"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_task
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__task_id
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_search
msgid "Task"
msgstr "Tasca"
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__stage_id
msgid "Task stage"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__tag_ids
msgid "Task tags"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:134
#, python-format
msgid ""
"The employee (%s) doesn't have a timezone, this might cause errors in the "
"time computation. It is configurable on the user linked to the employee"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_res_company__forecast_span
#: model:ir.model.fields,field_description:project_forecast.field_res_config_settings__forecast_span
msgid "Time Span"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__forecast_uom
#: model:ir.model.fields,field_description:project_forecast.field_res_company__forecast_uom
#: model:ir.model.fields,field_description:project_forecast.field_res_config_settings__forecast_uom
msgid "Time Unit"
msgstr ""
#. module: project_forecast
#: model:ir.filters,name:project_forecast.ir_filter_forecast_time_by_month
msgid "Time by month"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_forecast__user_id
msgid "User"
msgstr "Usuari"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_grid_by_task
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Week"
msgstr "Setmana"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_grid_by_task
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_project
#: model_terms:ir.ui.view,arch_db:project_forecast.view_project_forecast_grid_by_user
msgid "Year"
msgstr "Any"
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:21
#, python-format
msgid ""
"You cannot delete a project containing forecasts. You can either delete all "
"the project's forecasts and then delete the project or simply deactivate the"
" project."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:43
#, python-format
msgid ""
"You cannot delete a task containing forecasts. You can either delete all the"
" task's forecasts and then delete the task or simply deactivate the task."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:172
#, python-format
msgid "Your task is not in the selected project."
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_form
msgid "day(s)"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:108
#, python-format
msgid "undefined"
msgstr ""

135
project_forecast/i18n/cs.po Normal file
View File

@ -0,0 +1,135 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_forecast
#
# Translators:
# Martin Trigaux, 2019
# trendspotter <j.podhorecky@volny.cz>, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~12.5+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-27 09:19+0000\n"
"PO-Revision-Date: 2019-08-26 09:37+0000\n"
"Last-Translator: trendspotter <j.podhorecky@volny.cz>, 2019\n"
"Language-Team: Czech (https://www.transifex.com/odoo/teams/41243/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: cs\n"
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_kanban_inherit_project_forecast
msgid "<span class=\"o_label\">Planning</span>"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
msgid "<span>Planning</span>"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_task__allow_forecast
msgid "Allow Planning"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_user
msgid "By Employee"
msgstr "Podle zaměstnance"
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.planning_menu_schedule_by_project
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_project
msgid "By Project"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_project__allow_forecast
#: model:ir.model.fields,help:project_forecast.field_project_task__allow_forecast
msgid "Enable planning tasks on the project."
msgstr ""
#. module: project_forecast
#: model:ir.model.constraint,message:project_forecast.constraint_planning_slot_project_required_if_task
msgid "If the planning is linked to a task, the project must be set too."
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_from_project
#: model:ir.model.fields,field_description:project_forecast.field_project_project__allow_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_menu
msgid "Planning"
msgstr "Plánování"
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_analysis
#: model:ir.ui.menu,name:project_forecast.project_forecast_report_activities
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_graph
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_pivot
msgid "Planning Analysis"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.planning_action_schedule_by_project
msgid "Planning Schedule"
msgstr "Plánovací plán"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_planning_slot
msgid "Planning Shift"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_user
msgid "Planning by Employee"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_project
msgid "Planning by project"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_project
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__project_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Project"
msgstr "Projekt"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_task
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__task_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Task"
msgstr "Úloha"
#. module: project_forecast
#: model:ir.filters,name:project_forecast.ir_filter_forecast_time_by_month
msgid "Time by month"
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a project containing plannings. You can either delete all "
"the project's forecasts and then delete the project or simply deactivate the"
" project."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a task containing plannings. You can either delete all the"
" task's plannings and then delete the task or simply deactivate the task."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:0
#, python-format
msgid "Your task is not in the selected project."
msgstr "Váš úkol není ve vybraném projektu."

137
project_forecast/i18n/da.po Normal file
View File

@ -0,0 +1,137 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_forecast
#
# Translators:
# Joe Hansen <joedalton2@yahoo.dk>, 2019
# Martin Trigaux, 2019
# Sanne Kristensen <sanne@vkdata.dk>, 2019
# lhmflexerp <lhm@flexerp.dk>, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~12.5+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-27 09:19+0000\n"
"PO-Revision-Date: 2019-08-26 09:37+0000\n"
"Last-Translator: lhmflexerp <lhm@flexerp.dk>, 2019\n"
"Language-Team: Danish (https://www.transifex.com/odoo/teams/41243/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: da\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_kanban_inherit_project_forecast
msgid "<span class=\"o_label\">Planning</span>"
msgstr ""
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
msgid "<span>Planning</span>"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_task__allow_forecast
msgid "Allow Planning"
msgstr ""
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_user
msgid "By Employee"
msgstr "Pr. medarbejder"
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.planning_menu_schedule_by_project
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_project
msgid "By Project"
msgstr "Pr. projekt"
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_project__allow_forecast
#: model:ir.model.fields,help:project_forecast.field_project_task__allow_forecast
msgid "Enable planning tasks on the project."
msgstr ""
#. module: project_forecast
#: model:ir.model.constraint,message:project_forecast.constraint_planning_slot_project_required_if_task
msgid "If the planning is linked to a task, the project must be set too."
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_from_project
#: model:ir.model.fields,field_description:project_forecast.field_project_project__allow_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_menu
msgid "Planning"
msgstr "Planlægning"
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_analysis
#: model:ir.ui.menu,name:project_forecast.project_forecast_report_activities
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_graph
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_pivot
msgid "Planning Analysis"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.planning_action_schedule_by_project
msgid "Planning Schedule"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_planning_slot
msgid "Planning Shift"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_user
msgid "Planning by Employee"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_project
msgid "Planning by project"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_project
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__project_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Project"
msgstr "Projekt"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_task
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__task_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Task"
msgstr "Opgave"
#. module: project_forecast
#: model:ir.filters,name:project_forecast.ir_filter_forecast_time_by_month
msgid "Time by month"
msgstr "Tid pr. måned"
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a project containing plannings. You can either delete all "
"the project's forecasts and then delete the project or simply deactivate the"
" project."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a task containing plannings. You can either delete all the"
" task's plannings and then delete the task or simply deactivate the task."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:0
#, python-format
msgid "Your task is not in the selected project."
msgstr "Din opgave er ikke i det valgte projekt."

136
project_forecast/i18n/de.po Normal file
View File

@ -0,0 +1,136 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_forecast
#
# Translators:
# Chris Egal <sodaswed@web.de>, 2019
# Martin Trigaux, 2019
# Leon Grill <leg@odoo.com>, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~12.5+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-27 09:19+0000\n"
"PO-Revision-Date: 2019-08-26 09:37+0000\n"
"Last-Translator: Leon Grill <leg@odoo.com>, 2019\n"
"Language-Team: German (https://www.transifex.com/odoo/teams/41243/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_kanban_inherit_project_forecast
msgid "<span class=\"o_label\">Planning</span>"
msgstr "<span class=\"o_label\">Planung</span>"
#. module: project_forecast
#: model_terms:ir.ui.view,arch_db:project_forecast.project_view_form_inherit_project_forecast
msgid "<span>Planning</span>"
msgstr ""
#. module: project_forecast
#: model:ir.model.fields,field_description:project_forecast.field_project_task__allow_forecast
msgid "Allow Planning"
msgstr "Planung zulassen"
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_user
msgid "By Employee"
msgstr "Nach Mitarbeiter"
#. module: project_forecast
#: model:ir.ui.menu,name:project_forecast.planning_menu_schedule_by_project
#: model:ir.ui.menu,name:project_forecast.project_forecast_group_by_project
msgid "By Project"
msgstr "Nach Projekt"
#. module: project_forecast
#: model:ir.model.fields,help:project_forecast.field_project_project__allow_forecast
#: model:ir.model.fields,help:project_forecast.field_project_task__allow_forecast
msgid "Enable planning tasks on the project."
msgstr ""
#. module: project_forecast
#: model:ir.model.constraint,message:project_forecast.constraint_planning_slot_project_required_if_task
msgid "If the planning is linked to a task, the project must be set too."
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_from_project
#: model:ir.model.fields,field_description:project_forecast.field_project_project__allow_forecast
#: model:ir.ui.menu,name:project_forecast.project_forecast_menu
msgid "Planning"
msgstr "Planung"
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_analysis
#: model:ir.ui.menu,name:project_forecast.project_forecast_report_activities
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_graph
#: model_terms:ir.ui.view,arch_db:project_forecast.project_forecast_view_pivot
msgid "Planning Analysis"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.planning_action_schedule_by_project
msgid "Planning Schedule"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_planning_slot
msgid "Planning Shift"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_user
msgid "Planning by Employee"
msgstr ""
#. module: project_forecast
#: model:ir.actions.act_window,name:project_forecast.project_forecast_action_by_project
msgid "Planning by project"
msgstr ""
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_project
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__project_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Project"
msgstr "Projekt"
#. module: project_forecast
#: model:ir.model,name:project_forecast.model_project_task
#: model:ir.model.fields,field_description:project_forecast.field_planning_slot__task_id
#: model_terms:ir.ui.view,arch_db:project_forecast.planning_slot_view_search
msgid "Task"
msgstr "Aufgabe"
#. module: project_forecast
#: model:ir.filters,name:project_forecast.ir_filter_forecast_time_by_month
msgid "Time by month"
msgstr "Zeit im Monat"
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a project containing plannings. You can either delete all "
"the project's forecasts and then delete the project or simply deactivate the"
" project."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project.py:0
#, python-format
msgid ""
"You cannot delete a task containing plannings. You can either delete all the"
" task's plannings and then delete the task or simply deactivate the task."
msgstr ""
#. module: project_forecast
#: code:addons/project_forecast/models/project_forecast.py:0
#, python-format
msgid "Your task is not in the selected project."
msgstr "Ihre Aufgabe ist nicht im ausgewählten Projekt."

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