mirror of
https://github.com/OCA/contract.git
synced 2025-02-13 17:57:24 +02:00
[FIX] merged with latest version
This commit is contained in:
@@ -43,6 +43,7 @@ you can track and follow how much has been used.
|
||||
"analytic"
|
||||
],
|
||||
"data": [
|
||||
"report.xml",
|
||||
"hours_block_view.xml",
|
||||
"hours_block_data.xml",
|
||||
"hours_block_menu.xml",
|
||||
|
||||
5
project_sla/__init__.py
Normal file
5
project_sla/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import project_sla
|
||||
import analytic_account
|
||||
import project_sla_control
|
||||
import project_issue
|
||||
132
project_sla/__openerp__.py
Normal file
132
project_sla/__openerp__.py
Normal file
@@ -0,0 +1,132 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2013 Daniel Reis
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
{
|
||||
'name': 'Service Level Agreements',
|
||||
'summary': 'Define SLAs for your Contracts',
|
||||
'version': '1.0',
|
||||
"category": "Project Management",
|
||||
'description': """\
|
||||
Contract SLAs
|
||||
===============
|
||||
|
||||
SLAs are assigned to Contracts, on the Analytic Account form, SLA Definition
|
||||
separator. This is also where new SLA Definitions are created.
|
||||
|
||||
One Contract can have several SLA Definitions attached, allowing for
|
||||
"composite SLAs". For example, a contract could have a Response Time SLA (time
|
||||
to start resolution) and a Resolution Time SLA (time to close request).
|
||||
|
||||
|
||||
SLA Controlled Documents
|
||||
========================
|
||||
|
||||
Only Project Issue documents are made SLA controllable.
|
||||
However, a framework is made available to easily build extensions to make
|
||||
other documents models SLA controlled.
|
||||
|
||||
SLA controlled documents have attached information on the list of SLA rules
|
||||
they should meet (more than one in the case for composite SLAs) and a summary
|
||||
SLA status:
|
||||
|
||||
* "watching" the service level (it has SLA requirements to meet)
|
||||
* under "warning" (limit dates are close, special attention is needed)
|
||||
* "failed" (one on the SLA limits has not been met)
|
||||
* "achieved" (all SLA limits have been met)
|
||||
|
||||
Transient states, such as "watching" and "warning", are regularly updated by
|
||||
a hourly scheduled job, that reevaluates the warning and limit dates against
|
||||
the current time and changes the state when find dates that have been exceeded.
|
||||
|
||||
To decide what SLA Definitions apply for a specific document, first a lookup
|
||||
is made for a ``analytic_account_id`` field. If not found, then it will
|
||||
look up for the ``project_id`` and it's corresponding ``analytic_account_id``.
|
||||
|
||||
Specifically, the Service Desk module introduces a Analytic Account field for
|
||||
Project Issues. This makes it possible for a Service Team (a "Project") to
|
||||
have a generic SLA, but at the same time allow for some Contracts to have
|
||||
specific SLAs (such as the case for "premium" service conditions).
|
||||
|
||||
|
||||
SLA Definitions and Rules
|
||||
=========================
|
||||
|
||||
New SLA Definitions are created from the Analytic Account form, SLA Definition
|
||||
field.
|
||||
|
||||
Each definition can have one or more Rules.
|
||||
The particular rule to use is decided by conditions, so that you can set
|
||||
different service levels based on request attributes, such as Priority or
|
||||
Category.
|
||||
Each rule condition is evaluated in "sequence" order, and the first onea to met
|
||||
is the one to be used.
|
||||
In the simplest case, a single rule with no condition is just what is needed.
|
||||
|
||||
Each rule sets a number of hours until the "limit date", and the number of
|
||||
hours until a "warning date". The former will be used to decide if the SLA
|
||||
was achieved, and the later can be used for automatic alarms or escalation
|
||||
procedures.
|
||||
|
||||
Time will be counted from creation date, until the "Control Date" specified for
|
||||
the SLA Definition. That would usually be the "Close" (time until resolution)
|
||||
or the "Open" (time until response) dates.
|
||||
|
||||
The working calendar set in the related Project definitions will be used (see
|
||||
the "Other Info" tab). If none is defined, a builtin "all days, 8-12 13-17"
|
||||
default calendar is used.
|
||||
|
||||
A timezone and leave calendars will also used, based on either the assigned
|
||||
user (document's `user_id`) or on the current user.
|
||||
|
||||
|
||||
Setup checklist
|
||||
===============
|
||||
|
||||
The basic steps to configure SLAs for a Project are:
|
||||
|
||||
* Set Project's Working Calendar, at Project definitions, "Other Info" tab
|
||||
* Go to the Project's Analytic Account form; create and set SLA Definitions
|
||||
* Use the "Reapply SLAs" button on the Analytic Account form
|
||||
* See Project Issue's calculated SLAs in the new "Service Levels" tab
|
||||
|
||||
|
||||
Credits and Contributors
|
||||
========================
|
||||
|
||||
* Daniel Reis (https://launchpad.net/~dreis-pt)
|
||||
* David Vignoni, author of the icon from the KDE 3.x Nuvola icon theme
|
||||
""",
|
||||
'author': 'Daniel Reis',
|
||||
'website': '',
|
||||
'depends': [
|
||||
'project_issue',
|
||||
],
|
||||
'data': [
|
||||
'project_sla_view.xml',
|
||||
'project_sla_control_view.xml',
|
||||
'project_sla_control_data.xml',
|
||||
'analytic_account_view.xml',
|
||||
'project_view.xml',
|
||||
'project_issue_view.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'demo': ['project_sla_demo.xml'],
|
||||
'test': ['test/project_sla.yml'],
|
||||
'installable': True,
|
||||
}
|
||||
69
project_sla/analytic_account.py
Normal file
69
project_sla/analytic_account.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2013 Daniel Reis
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import fields, orm
|
||||
|
||||
|
||||
class AnalyticAccount(orm.Model):
|
||||
""" Add SLA to Analytic Accounts """
|
||||
_inherit = 'account.analytic.account'
|
||||
_columns = {
|
||||
'sla_ids': fields.many2many(
|
||||
'project.sla', string='Service Level Agreement'),
|
||||
}
|
||||
|
||||
def _reapply_sla(self, cr, uid, ids, recalc_closed=False, context=None):
|
||||
"""
|
||||
Force SLA recalculation on open documents that already are subject to
|
||||
this SLA Definition.
|
||||
To use after changing a Contract SLA or it's Definitions.
|
||||
The ``recalc_closed`` flag allows to also recompute closed documents.
|
||||
"""
|
||||
ctrl_obj = self.pool.get('project.sla.control')
|
||||
proj_obj = self.pool.get('project.project')
|
||||
exclude_states = ['cancelled'] + (not recalc_closed and ['done'] or [])
|
||||
for contract in self.browse(cr, uid, ids, context=context):
|
||||
# for each contract, and for each model under SLA control ...
|
||||
for m_name in set([sla.control_model for sla in contract.sla_ids]):
|
||||
model = self.pool.get(m_name)
|
||||
doc_ids = []
|
||||
if 'analytic_account_id' in model._columns:
|
||||
doc_ids += model.search(
|
||||
cr, uid,
|
||||
[('analytic_account_id', '=', contract.id),
|
||||
('state', 'not in', exclude_states)],
|
||||
context=context)
|
||||
if 'project_id' in model._columns:
|
||||
proj_ids = proj_obj.search(
|
||||
cr, uid, [('analytic_account_id', '=', contract.id)],
|
||||
context=context)
|
||||
doc_ids += model.search(
|
||||
cr, uid,
|
||||
[('project_id', 'in', proj_ids),
|
||||
('state', 'not in', exclude_states)],
|
||||
context=context)
|
||||
if doc_ids:
|
||||
docs = model.browse(cr, uid, doc_ids, context=context)
|
||||
ctrl_obj.store_sla_control(cr, uid, docs, context=context)
|
||||
return True
|
||||
|
||||
def reapply_sla(self, cr, uid, ids, context=None):
|
||||
""" Reapply SLAs button action """
|
||||
return self._reapply_sla(cr, uid, ids, context=context)
|
||||
24
project_sla/analytic_account_view.xml
Normal file
24
project_sla/analytic_account_view.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="view_account_analytic_account_form_sla" model="ir.ui.view">
|
||||
<field name="name">view_account_analytic_account_form_sla</field>
|
||||
<field name="model">account.analytic.account</field>
|
||||
<field name="inherit_id" ref="analytic.view_account_analytic_account_form"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<page name="contract_page" position="after">
|
||||
<page name="sla_page" string="Service Level Agreement">
|
||||
<field name="sla_ids" nolabel="1"/>
|
||||
<button name="reapply_sla" string="Reapply" type="object"
|
||||
help="Reapply the SLAs to all Contract's documents."
|
||||
groups="project.group_project_manager" />
|
||||
</page>
|
||||
</page>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
296
project_sla/i18n/project_sla.pot
Normal file
296
project_sla/i18n/project_sla.pot
Normal file
@@ -0,0 +1,296 @@
|
||||
# Translation of OpenERP Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * project_sla
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: OpenERP Server 7.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2013-12-19 10:28+0000\n"
|
||||
"PO-Revision-Date: 2013-12-19 10:28+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: project_sla
|
||||
#: model:project.sla,name:project_sla.sla_response
|
||||
msgid "Standard Response Time"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: help:project.sla,control_field_id:0
|
||||
msgid "Date field used to check if the SLA was achieved."
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: model:ir.model,name:project_sla.model_project_issue
|
||||
msgid "Project Issue"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: model:ir.model,name:project_sla.model_project_sla_control
|
||||
msgid "SLA Control Registry"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: view:project.sla:0
|
||||
msgid "Reapply SLA on Contracts"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: view:project.project:0
|
||||
msgid "Administration"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: view:project.issue:0
|
||||
msgid "Priority"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: selection:project.issue,sla_state:0
|
||||
#: selection:project.sla.control,sla_state:0
|
||||
#: selection:project.sla.controlled,sla_state:0
|
||||
msgid "Failed"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla.control,sla_warn_date:0
|
||||
msgid "Warning Date"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla.line,warn_qty:0
|
||||
msgid "Hours to Warn"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: view:project.sla.control:0
|
||||
msgid "Service Level"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: selection:project.issue,sla_state:0
|
||||
#: selection:project.sla.control,sla_state:0
|
||||
#: selection:project.sla.controlled,sla_state:0
|
||||
msgid "Watching"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: view:project.sla:0
|
||||
#: field:project.sla,analytic_ids:0
|
||||
msgid "Contracts"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla,name:0
|
||||
#: field:project.sla.line,name:0
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla,active:0
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.issue,sla_control_ids:0
|
||||
#: field:project.sla.controlled,sla_control_ids:0
|
||||
msgid "SLA Control"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla.control,sla_achieved:0
|
||||
msgid "Achieved?"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: view:project.issue:0
|
||||
#: field:project.issue,sla_state:0
|
||||
#: field:project.sla.control,sla_state:0
|
||||
#: field:project.sla.controlled,sla_state:0
|
||||
msgid "SLA Status"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla.line,condition:0
|
||||
msgid "Condition"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla,control_model:0
|
||||
msgid "For documents"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: view:project.sla:0
|
||||
#: field:project.sla.line,sla_id:0
|
||||
msgid "SLA Definition"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: model:project.sla.line,name:project_sla.sla_response_rule2
|
||||
msgid "Response in two business days"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: selection:project.issue,sla_state:0
|
||||
#: selection:project.sla.control,sla_state:0
|
||||
#: selection:project.sla.controlled,sla_state:0
|
||||
msgid "Will Fail"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla.control,sla_line_id:0
|
||||
msgid "Service Agreement"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla.control,doc_id:0
|
||||
msgid "Document ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla.control,locked:0
|
||||
msgid "Recalculation disabled"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla.control,sla_limit_date:0
|
||||
msgid "Limit Date"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: help:project.sla.line,condition:0
|
||||
msgid "Apply only if this expression is evaluated to True. The document fields can be accessed using either o, obj or object. Example: obj.priority <= '2'"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: model:project.sla.line,name:project_sla.sla_resolution_rule1
|
||||
msgid "Resolution in two business days"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: view:account.analytic.account:0
|
||||
msgid "Reapply"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla.line,limit_qty:0
|
||||
msgid "Hours to Limit"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla,sla_line_ids:0
|
||||
#: view:project.sla.line:0
|
||||
msgid "Definitions"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: model:project.sla.line,name:project_sla.sla_resolution_rule2
|
||||
msgid "Resolution in three business days"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla.control,sla_close_date:0
|
||||
msgid "Close Date"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: model:ir.model,name:project_sla.model_project_sla
|
||||
msgid "project.sla"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla,control_field_id:0
|
||||
msgid "Control Date"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: model:project.sla.line,name:project_sla.sla_response_rule1
|
||||
msgid "Response in one business day"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: selection:project.issue,sla_state:0
|
||||
#: selection:project.sla.control,sla_state:0
|
||||
#: selection:project.sla.controlled,sla_state:0
|
||||
msgid "Achieved"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: view:account.analytic.account:0
|
||||
#: field:account.analytic.account,sla_ids:0
|
||||
msgid "Service Level Agreement"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: model:project.sla,name:project_sla.sla_resolution
|
||||
msgid "Standard Resolution Time"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla.line,sequence:0
|
||||
msgid "Sequence"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: view:project.issue:0
|
||||
msgid "Service Levels"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: model:ir.model,name:project_sla.model_account_analytic_account
|
||||
msgid "Analytic Account"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: view:project.sla:0
|
||||
msgid "Rules"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: help:project.sla.control,locked:0
|
||||
msgid "Safeguard manual changes from future automatic recomputations."
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: selection:project.issue,sla_state:0
|
||||
#: selection:project.sla.control,sla_state:0
|
||||
#: selection:project.sla.controlled,sla_state:0
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: view:project.issue:0
|
||||
msgid "Extra Info"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla.control,doc_model:0
|
||||
msgid "Document Model"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: model:ir.model,name:project_sla.model_project_sla_line
|
||||
msgid "project.sla.line"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: view:account.analytic.account:0
|
||||
msgid "Reapply the SLAs to all Contract's documents."
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: field:project.sla.control,sla_start_date:0
|
||||
msgid "Start Date"
|
||||
msgstr ""
|
||||
|
||||
#. module: project_sla
|
||||
#: model:ir.model,name:project_sla.model_project_sla_controlled
|
||||
msgid "SLA Controlled Document"
|
||||
msgstr ""
|
||||
|
||||
BIN
project_sla/images/10_sla_contract.png
Normal file
BIN
project_sla/images/10_sla_contract.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
project_sla/images/20_sla_definition.png
Normal file
BIN
project_sla/images/20_sla_definition.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
project_sla/images/30_sla_controlled.png
Normal file
BIN
project_sla/images/30_sla_controlled.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
75
project_sla/m2m.py
Normal file
75
project_sla/m2m.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
Wrapper for OpenERP's cryptic write conventions for x2many fields.
|
||||
|
||||
Example usage:
|
||||
|
||||
import m2m
|
||||
browse_rec.write({'many_ids: m2m.clear())
|
||||
browse_rec.write({'many_ids: m2m.link(99))
|
||||
browse_rec.write({'many_ids: m2m.add({'name': 'Monty'}))
|
||||
browse_rec.write({'many_ids: m2m.replace([98, 99]))
|
||||
|
||||
Since returned values are lists, the can be joined using the plus operator:
|
||||
|
||||
browse_rec.write({'many_ids: m2m.clear() + m2m.link(99))
|
||||
|
||||
(Source: https://github.com/dreispt/openerp-write2many)
|
||||
"""
|
||||
|
||||
|
||||
def create(values):
|
||||
""" Create a referenced record """
|
||||
assert isinstance(values, dict)
|
||||
return [(0, 0, values)]
|
||||
|
||||
|
||||
def add(values):
|
||||
""" Intuitive alias for create() """
|
||||
return create(values)
|
||||
|
||||
|
||||
def write(id, values):
|
||||
""" Write on referenced record """
|
||||
assert isinstance(id, int)
|
||||
assert isinstance(values, dict)
|
||||
return [(1, id, values)]
|
||||
|
||||
|
||||
def remove(id):
|
||||
""" Unlink and delete referenced record """
|
||||
assert isinstance(id, int)
|
||||
return [(2, id)]
|
||||
|
||||
|
||||
def unlink(id):
|
||||
""" Unlink but do not delete the referenced record """
|
||||
assert isinstance(id, int)
|
||||
return [(3, id)]
|
||||
|
||||
|
||||
def link(id):
|
||||
""" Link but do not delete the referenced record """
|
||||
assert isinstance(id, int)
|
||||
return [(4, id)]
|
||||
|
||||
|
||||
def clear():
|
||||
""" Unlink all referenced records (doesn't delete them) """
|
||||
return [(5, 0)]
|
||||
|
||||
|
||||
def replace(ids):
|
||||
""" Unlink all current records and replace them with a new list """
|
||||
assert isinstance(ids, list)
|
||||
return [(6, 0, ids)]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Tests:
|
||||
assert create({'name': 'Monty'}) == [(0, 0, {'name': 'Monty'})]
|
||||
assert write(99, {'name': 'Monty'}) == [(1, 99, {'name': 'Monty'})]
|
||||
assert remove(99) == [(2, 99)]
|
||||
assert unlink(99) == [(3, 99)]
|
||||
assert clear() == [(5, 0)]
|
||||
assert replace([97, 98, 99]) == [(6, 0, [97, 98, 99])]
|
||||
print("Done!")
|
||||
29
project_sla/project_issue.py
Normal file
29
project_sla/project_issue.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2013 Daniel Reis
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import orm
|
||||
|
||||
|
||||
class ProjectIssue(orm.Model):
|
||||
"""
|
||||
Extend Project Issues to be SLA Controlled
|
||||
"""
|
||||
_name = 'project.issue'
|
||||
_inherit = ['project.issue', 'project.sla.controlled']
|
||||
60
project_sla/project_issue_view.xml
Normal file
60
project_sla/project_issue_view.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- Project Issue Form -->
|
||||
<record id="project_issue_form_view_sla" model="ir.ui.view">
|
||||
<field name="name">project_issue_form_view_sla</field>
|
||||
<field name="model">project.issue</field>
|
||||
<field name="inherit_id" ref="project_issue.project_issue_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<page string="Extra Info" position="after">
|
||||
<page name="sla_page" string="Service Levels"
|
||||
attrs="{'invisible': [('sla_state', '=', False)]}">
|
||||
<group>
|
||||
<group>
|
||||
<field name="sla_state" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="write_date" />
|
||||
</group>
|
||||
</group>
|
||||
<field name="sla_control_ids"/>
|
||||
</page>
|
||||
</page>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Project Issue List -->
|
||||
<record model="ir.ui.view" id="project_issue_tree_view_sla">
|
||||
<field name="name">project_issue_tree_view_sla</field>
|
||||
<field name="model">project.issue</field>
|
||||
<field name="inherit_id" ref="project_issue.project_issue_tree_view"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<field name="project_id" position="after">
|
||||
<field name="sla_state"/>
|
||||
</field>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Project Issue Filter -->
|
||||
<record id="view_project_issue_filter_sdesk" model="ir.ui.view">
|
||||
<field name="name">view_project_issue_filter_sdesk</field>
|
||||
<field name="model">project.issue</field>
|
||||
<field name="inherit_id" ref="project_issue.view_project_issue_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<filter string="Priority" position="after">
|
||||
<filter string="SLA Status" context="{'group_by':'sla_state'}" />
|
||||
</filter>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
86
project_sla/project_sla.py
Normal file
86
project_sla/project_sla.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2013 Daniel Reis
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import fields, orm
|
||||
|
||||
|
||||
class SLADefinition(orm.Model):
|
||||
"""
|
||||
SLA Definition
|
||||
"""
|
||||
_name = 'project.sla'
|
||||
_description = 'SLA Definition'
|
||||
_columns = {
|
||||
'name': fields.char('Title', size=64, required=True, translate=True),
|
||||
'active': fields.boolean('Active'),
|
||||
'control_model': fields.char('For documents', size=128, required=True),
|
||||
'control_field_id': fields.many2one(
|
||||
'ir.model.fields', 'Control Date', required=True,
|
||||
domain="[('model_id.model', '=', control_model),"
|
||||
" ('ttype', 'in', ['date', 'datetime'])]",
|
||||
help="Date field used to check if the SLA was achieved."),
|
||||
'sla_line_ids': fields.one2many(
|
||||
'project.sla.line', 'sla_id', 'Definitions'),
|
||||
'analytic_ids': fields.many2many(
|
||||
'account.analytic.account', string='Contracts'),
|
||||
}
|
||||
_defaults = {
|
||||
'active': True,
|
||||
}
|
||||
|
||||
def _reapply_slas(self, cr, uid, ids, recalc_closed=False, context=None):
|
||||
"""
|
||||
Force SLA recalculation on all _open_ Contracts for the selected SLAs.
|
||||
To use upon SLA Definition modifications.
|
||||
"""
|
||||
contract_obj = self.pool.get('account.analytic.account')
|
||||
for sla in self.browse(cr, uid, ids, context=context):
|
||||
contr_ids = [x.id for x in sla.analytic_ids if x.state == 'open']
|
||||
contract_obj._reapply_sla(
|
||||
cr, uid, contr_ids, recalc_closed=recalc_closed,
|
||||
context=context)
|
||||
return True
|
||||
|
||||
def reapply_slas(self, cr, uid, ids, context=None):
|
||||
""" Reapply SLAs button action """
|
||||
return self._reapply_slas(cr, uid, ids, context=context)
|
||||
|
||||
|
||||
class SLARules(orm.Model):
|
||||
"""
|
||||
SLA Definition Rule Lines
|
||||
"""
|
||||
_name = 'project.sla.line'
|
||||
_definition = 'SLA Definition Rule Lines'
|
||||
_order = 'sla_id,sequence'
|
||||
_columns = {
|
||||
'sla_id': fields.many2one('project.sla', 'SLA Definition'),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'name': fields.char('Title', size=64, required=True, translate=True),
|
||||
'condition': fields.char(
|
||||
'Condition', size=256, help="Apply only if this expression is "
|
||||
"evaluated to True. The document fields can be accessed using "
|
||||
"either o, obj or object. Example: obj.priority <= '2'"),
|
||||
'limit_qty': fields.integer('Hours to Limit'),
|
||||
'warn_qty': fields.integer('Hours to Warn'),
|
||||
}
|
||||
_defaults = {
|
||||
'sequence': 10,
|
||||
}
|
||||
322
project_sla/project_sla_control.py
Normal file
322
project_sla/project_sla_control.py
Normal file
@@ -0,0 +1,322 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2013 Daniel Reis
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import fields, orm
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT as DT_FMT
|
||||
from openerp import SUPERUSER_ID
|
||||
from datetime import timedelta
|
||||
from datetime import datetime as dt
|
||||
import m2m
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SLA_STATES = [('5', 'Failed'), ('4', 'Will Fail'), ('3', 'Warning'),
|
||||
('2', 'Watching'), ('1', 'Achieved')]
|
||||
|
||||
|
||||
def safe_getattr(obj, dotattr, default=False):
|
||||
"""
|
||||
Follow an object attribute dot-notation chain to find the leaf value.
|
||||
If any attribute doesn't exist or has no value, just return False.
|
||||
Checks hasattr ahead, to avoid ORM Browse log warnings.
|
||||
"""
|
||||
attrs = dotattr.split('.')
|
||||
while attrs:
|
||||
attr = attrs.pop(0)
|
||||
if attr in obj._model._columns:
|
||||
try:
|
||||
obj = getattr(obj, attr)
|
||||
except AttributeError:
|
||||
return default
|
||||
if not obj:
|
||||
return default
|
||||
else:
|
||||
return default
|
||||
return obj
|
||||
|
||||
|
||||
class SLAControl(orm.Model):
|
||||
"""
|
||||
SLA Control Registry
|
||||
Each controlled document (Issue, Claim, ...) will have a record here.
|
||||
This model concentrates all the logic for Service Level calculation.
|
||||
"""
|
||||
_name = 'project.sla.control'
|
||||
_description = 'SLA Control Registry'
|
||||
|
||||
_columns = {
|
||||
'doc_id': fields.integer('Document ID', readonly=True),
|
||||
'doc_model': fields.char('Document Model', size=128, readonly=True),
|
||||
'sla_line_id': fields.many2one(
|
||||
'project.sla.line', 'Service Agreement'),
|
||||
'sla_warn_date': fields.datetime('Warning Date'),
|
||||
'sla_limit_date': fields.datetime('Limit Date'),
|
||||
'sla_start_date': fields.datetime('Start Date'),
|
||||
'sla_close_date': fields.datetime('Close Date'),
|
||||
'sla_achieved': fields.integer('Achieved?'),
|
||||
'sla_state': fields.selection(SLA_STATES, string="SLA Status"),
|
||||
'locked': fields.boolean(
|
||||
'Recalculation disabled',
|
||||
help="Safeguard manual changes from future automatic "
|
||||
"recomputations."),
|
||||
# Future: perfect SLA manual handling
|
||||
}
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
"""
|
||||
Update the related Document's SLA State when any of the SLA Control
|
||||
lines changes state
|
||||
"""
|
||||
res = super(SLAControl, self).write(
|
||||
cr, uid, ids, vals, context=context)
|
||||
new_state = vals.get('sla_state')
|
||||
if new_state:
|
||||
# just update sla_state without recomputing the whole thing
|
||||
context = context or {}
|
||||
context['__sla_stored__'] = 1
|
||||
for sla in self.browse(cr, uid, ids, context=context):
|
||||
doc = self.pool.get(sla.doc_model).browse(
|
||||
cr, uid, sla.doc_id, context=context)
|
||||
if doc.sla_state < new_state:
|
||||
doc.write({'sla_state': new_state})
|
||||
return res
|
||||
|
||||
def update_sla_states(self, cr, uid, context=None):
|
||||
"""
|
||||
Updates SLA States, given the current datetime:
|
||||
Only works on "open" sla states (watching, warning and will fail):
|
||||
- exceeded limit date are set to "will fail"
|
||||
- exceeded warning dates are set to "warning"
|
||||
To be used by a scheduled job.
|
||||
"""
|
||||
now = dt.now().strftime(DT_FMT)
|
||||
# SLAs to mark as "will fail"
|
||||
control_ids = self.search(
|
||||
cr, uid,
|
||||
[('sla_state', 'in', ['2', '3']), ('sla_limit_date', '<', now)],
|
||||
context=context)
|
||||
self.write(cr, uid, control_ids, {'sla_state': '4'}, context=context)
|
||||
# SLAs to mark as "warning"
|
||||
control_ids = self.search(
|
||||
cr, uid,
|
||||
[('sla_state', 'in', ['2']), ('sla_warn_date', '<', now)],
|
||||
context=context)
|
||||
self.write(cr, uid, control_ids, {'sla_state': '3'}, context=context)
|
||||
return True
|
||||
|
||||
def _compute_sla_date(self, cr, uid, working_hours, res_uid,
|
||||
start_date, hours, context=None):
|
||||
"""
|
||||
Return a limit datetime by adding hours to a start_date, honoring
|
||||
a working_time calendar and a resource's (res_uid) timezone and
|
||||
availability (leaves)
|
||||
|
||||
Currently implemented using a binary search using
|
||||
_interval_hours_get() from resource.calendar. This is
|
||||
resource.calendar agnostic, but could be more efficient if
|
||||
implemented based on it's logic.
|
||||
|
||||
Known issue: the end date can be a non-working time; it would be
|
||||
best for it to be the latest working time possible. Example:
|
||||
if working time is 08:00 - 16:00 and start_date is 19:00, the +8h
|
||||
end date will be 19:00 of the next day, and it should rather be
|
||||
16:00 of the next day.
|
||||
"""
|
||||
assert isinstance(start_date, dt)
|
||||
assert isinstance(hours, int) and hours >= 0
|
||||
|
||||
cal_obj = self.pool.get('resource.calendar')
|
||||
target, step = hours * 3600, 16 * 3600
|
||||
lo, hi = start_date, start_date
|
||||
while target > 0 and step > 60:
|
||||
hi = lo + timedelta(seconds=step)
|
||||
check = int(3600 * cal_obj._interval_hours_get(
|
||||
cr, uid, working_hours, lo, hi,
|
||||
timezone_from_uid=res_uid, exclude_leaves=False,
|
||||
context=context))
|
||||
if check <= target:
|
||||
target -= check
|
||||
lo = hi
|
||||
else:
|
||||
step = int(step / 4.0)
|
||||
return hi
|
||||
|
||||
def _get_computed_slas(self, cr, uid, doc, context=None):
|
||||
"""
|
||||
Returns a dict with the computed data for SLAs, given a browse record
|
||||
for the target document.
|
||||
|
||||
* The SLA used is either from a related analytic_account_id or
|
||||
project_id, whatever is found first.
|
||||
* The work calendar is taken from the Project's definitions ("Other
|
||||
Info" tab -> Working Time).
|
||||
* The timezone used for the working time calculations are from the
|
||||
document's responsible User (user_id) or from the current User (uid).
|
||||
|
||||
For the SLA Achieved calculation:
|
||||
|
||||
* Creation date is used to start counting time
|
||||
* Control date, used to calculate SLA achievement, is defined in the
|
||||
SLA Definition rules.
|
||||
"""
|
||||
def datetime2str(dt_value, fmt): # tolerant datetime to string
|
||||
return dt_value and dt.strftime(dt_value, fmt) or None
|
||||
|
||||
res = []
|
||||
sla_ids = (safe_getattr(doc, 'analytic_account_id.sla_ids') or
|
||||
safe_getattr(doc, 'project_id.analytic_account_id.sla_ids'))
|
||||
if not sla_ids:
|
||||
return res
|
||||
|
||||
for sla in sla_ids:
|
||||
if sla.control_model != doc._table_name:
|
||||
continue # SLA not for this model; skip
|
||||
|
||||
for l in sla.sla_line_ids:
|
||||
eval_context = {'o': doc, 'obj': doc, 'object': doc}
|
||||
if not l.condition or safe_eval(l.condition, eval_context):
|
||||
start_date = dt.strptime(doc.create_date, DT_FMT)
|
||||
res_uid = doc.user_id.id or uid
|
||||
cal = safe_getattr(
|
||||
doc, 'project_id.resource_calendar_id.id')
|
||||
warn_date = self._compute_sla_date(
|
||||
cr, uid, cal, res_uid, start_date, l.warn_qty,
|
||||
context=context)
|
||||
lim_date = self._compute_sla_date(
|
||||
cr, uid, cal, res_uid, warn_date,
|
||||
l.limit_qty - l.warn_qty,
|
||||
context=context)
|
||||
# evaluate sla state
|
||||
control_val = getattr(doc, sla.control_field_id.name)
|
||||
if control_val:
|
||||
control_date = dt.strptime(control_val, DT_FMT)
|
||||
if control_date > lim_date:
|
||||
sla_val, sla_state = 0, '5' # failed
|
||||
else:
|
||||
sla_val, sla_state = 1, '1' # achieved
|
||||
else:
|
||||
control_date = None
|
||||
now = dt.now()
|
||||
if now > lim_date:
|
||||
sla_val, sla_state = 0, '4' # will fail
|
||||
elif now > warn_date:
|
||||
sla_val, sla_state = 0, '3' # warning
|
||||
else:
|
||||
sla_val, sla_state = 0, '2' # watching
|
||||
|
||||
res.append(
|
||||
{'sla_line_id': l.id,
|
||||
'sla_achieved': sla_val,
|
||||
'sla_state': sla_state,
|
||||
'sla_warn_date': datetime2str(warn_date, DT_FMT),
|
||||
'sla_limit_date': datetime2str(lim_date, DT_FMT),
|
||||
'sla_start_date': datetime2str(start_date, DT_FMT),
|
||||
'sla_close_date': datetime2str(control_date, DT_FMT),
|
||||
'doc_id': doc.id,
|
||||
'doc_model': sla.control_model})
|
||||
break
|
||||
|
||||
if sla_ids and not res:
|
||||
_logger.warning("No valid SLA rule found for %d, SLA Ids %s"
|
||||
% (doc.id, repr([x.id for x in sla_ids])))
|
||||
return res
|
||||
|
||||
def store_sla_control(self, cr, uid, docs, context=None):
|
||||
"""
|
||||
Used by controlled documents to ask for SLA calculation and storage.
|
||||
``docs`` is a Browse object
|
||||
"""
|
||||
# context flag to avoid infinite loops on further writes
|
||||
context = context or {}
|
||||
if '__sla_stored__' in context:
|
||||
return False
|
||||
else:
|
||||
context['__sla_stored__'] = 1
|
||||
|
||||
res = []
|
||||
for ix, doc in enumerate(docs):
|
||||
if ix and ix % 50 == 0:
|
||||
_logger.info('...%d SLAs recomputed for %s' % (ix, doc._name))
|
||||
control = {x.sla_line_id.id: x
|
||||
for x in doc.sla_control_ids}
|
||||
sla_recs = self._get_computed_slas(cr, uid, doc, context=context)
|
||||
# calc sla control lines
|
||||
if sla_recs:
|
||||
slas = []
|
||||
for sla_rec in sla_recs:
|
||||
sla_line_id = sla_rec.get('sla_line_id')
|
||||
if sla_line_id in control:
|
||||
control_rec = control.get(sla_line_id)
|
||||
if not control_rec.locked:
|
||||
slas += m2m.write(control_rec.id, sla_rec)
|
||||
else:
|
||||
slas += m2m.add(sla_rec)
|
||||
else:
|
||||
slas = m2m.clear()
|
||||
# calc sla control summary
|
||||
vals = {'sla_state': None, 'sla_control_ids': slas}
|
||||
if sla_recs and doc.sla_control_ids:
|
||||
vals['sla_state'] = max(
|
||||
x.sla_state for x in doc.sla_control_ids)
|
||||
# store sla
|
||||
doc._model.write( # regular users can't write on SLA Control
|
||||
cr, SUPERUSER_ID, [doc.id], vals, context=context)
|
||||
return res
|
||||
|
||||
|
||||
class SLAControlled(orm.AbstractModel):
|
||||
"""
|
||||
SLA Controlled documents: AbstractModel to apply SLA control on Models
|
||||
"""
|
||||
_name = 'project.sla.controlled'
|
||||
_description = 'SLA Controlled Document'
|
||||
_columns = {
|
||||
'sla_control_ids': fields.many2many(
|
||||
'project.sla.control', string="SLA Control", ondelete='cascade'),
|
||||
'sla_state': fields.selection(
|
||||
SLA_STATES, string="SLA Status", readonly=True),
|
||||
}
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
res = super(SLAControlled, self).create(cr, uid, vals, context=context)
|
||||
docs = self.browse(cr, uid, [res], context=context)
|
||||
self.pool.get('project.sla.control').store_sla_control(
|
||||
cr, uid, docs, context=context)
|
||||
return res
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
res = super(SLAControlled, self).write(
|
||||
cr, uid, ids, vals, context=context)
|
||||
docs = [x for x in self.browse(cr, uid, ids, context=context)
|
||||
if (x.state != 'cancelled') and
|
||||
(x.state != 'done' or x.sla_state not in ['1', '5'])]
|
||||
self.pool.get('project.sla.control').store_sla_control(
|
||||
cr, uid, docs, context=context)
|
||||
return res
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
# Unlink and delete all related Control records
|
||||
for doc in self.browse(cr, uid, ids, context=context):
|
||||
vals = [m2m.remove(x.id)[0] for x in doc.sla_control_ids]
|
||||
doc.write({'sla_control_ids': vals})
|
||||
return super(SLAControlled, self).unlink(cr, uid, ids, context=context)
|
||||
18
project_sla/project_sla_control_data.xml
Normal file
18
project_sla/project_sla_control_data.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="ir_cron_sla_action" model="ir.cron">
|
||||
<field name="name">Update SLA States</field>
|
||||
<field name="priority" eval="100"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">hours</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="model">project.sla.control</field>
|
||||
<field name="function">update_sla_states</field>
|
||||
<field name="args">()</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
25
project_sla/project_sla_control_view.xml
Normal file
25
project_sla/project_sla_control_view.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- List view used when the sla_control_ids field
|
||||
is added to controlled document's form -->
|
||||
<record id="view_sla_control_tree" model="ir.ui.view">
|
||||
<field name="name">view_sla_control_tree</field>
|
||||
<field name="model">project.sla.control</field>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<tree string="Service Level">
|
||||
<field name="sla_line_id"/>
|
||||
<field name="sla_state"/>
|
||||
<field name="sla_start_date"/>
|
||||
<field name="sla_warn_date"/>
|
||||
<field name="sla_limit_date"/>
|
||||
<field name="sla_close_date"/>
|
||||
</tree>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
138
project_sla/project_sla_demo.xml
Normal file
138
project_sla/project_sla_demo.xml
Normal file
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- Working Time calendar -->
|
||||
<record id="worktime_9_18" model="resource.calendar">
|
||||
<field name="name">Working Days 09-13 14-18</field>
|
||||
</record>
|
||||
<record id="worktime 9_18_0M" model="resource.calendar.attendance">
|
||||
<field name="dayofweek">0</field>
|
||||
<field name="name">Monday Morning</field>
|
||||
<field name="hour_from">9</field>
|
||||
<field name="hour_to">13</field>
|
||||
<field name="calendar_id" ref="worktime_9_18" />
|
||||
</record>
|
||||
<record id="worktime 9_18_0A" model="resource.calendar.attendance">
|
||||
<field name="dayofweek">0</field>
|
||||
<field name="name">Monday Afternoon</field>
|
||||
<field name="hour_from">14</field>
|
||||
<field name="hour_to">18</field>
|
||||
<field name="calendar_id" ref="worktime_9_18" />
|
||||
</record>
|
||||
<record id="worktime 9_18_1M" model="resource.calendar.attendance">
|
||||
<field name="dayofweek">1</field>
|
||||
<field name="name">Tuesday Morning</field>
|
||||
<field name="hour_from">9</field>
|
||||
<field name="hour_to">13</field>
|
||||
<field name="calendar_id" ref="worktime_9_18" />
|
||||
</record>
|
||||
<record id="worktime 9_18_1A" model="resource.calendar.attendance">
|
||||
<field name="dayofweek">1</field>
|
||||
<field name="name">Tuesday Afternoon</field>
|
||||
<field name="hour_from">14</field>
|
||||
<field name="hour_to">18</field>
|
||||
<field name="calendar_id" ref="worktime_9_18" />
|
||||
</record>
|
||||
<record id="worktime 9_18_2M" model="resource.calendar.attendance">
|
||||
<field name="dayofweek">2</field>
|
||||
<field name="name">Wednesday Morning</field>
|
||||
<field name="hour_from">9</field>
|
||||
<field name="hour_to">13</field>
|
||||
<field name="calendar_id" ref="worktime_9_18" />
|
||||
</record>
|
||||
<record id="worktime 9_18_2A" model="resource.calendar.attendance">
|
||||
<field name="dayofweek">2</field>
|
||||
<field name="name">Wednesday Afternoon</field>
|
||||
<field name="hour_from">14</field>
|
||||
<field name="hour_to">18</field>
|
||||
<field name="calendar_id" ref="worktime_9_18" />
|
||||
</record>
|
||||
<record id="worktime 9_18_3M" model="resource.calendar.attendance">
|
||||
<field name="dayofweek">3</field>
|
||||
<field name="name">Thursday Morning</field>
|
||||
<field name="hour_from">9</field>
|
||||
<field name="hour_to">13</field>
|
||||
<field name="calendar_id" ref="worktime_9_18" />
|
||||
</record>
|
||||
<record id="worktime 9_18_3A" model="resource.calendar.attendance">
|
||||
<field name="dayofweek">3</field>
|
||||
<field name="name">Thursday Afternoon</field>
|
||||
<field name="hour_from">14</field>
|
||||
<field name="hour_to">18</field>
|
||||
<field name="calendar_id" ref="worktime_9_18" />
|
||||
</record>
|
||||
<record id="worktime 9_18_4M" model="resource.calendar.attendance">
|
||||
<field name="dayofweek">4</field>
|
||||
<field name="name">Friday Morning</field>
|
||||
<field name="hour_from">9</field>
|
||||
<field name="hour_to">13</field>
|
||||
<field name="calendar_id" ref="worktime_9_18" />
|
||||
</record>
|
||||
<record id="worktime 9_18_4A" model="resource.calendar.attendance">
|
||||
<field name="dayofweek">4</field>
|
||||
<field name="name">Friday Afternoon</field>
|
||||
<field name="hour_from">14</field>
|
||||
<field name="hour_to">18</field>
|
||||
<field name="calendar_id" ref="worktime_9_18" />
|
||||
</record>
|
||||
|
||||
<!-- Set Project Calendar -->
|
||||
<record id="project.project_project_1" model="project.project">
|
||||
<field name="resource_calendar_id" ref="worktime_9_18" />
|
||||
</record>
|
||||
|
||||
<!-- SLA Definition and Rules -->
|
||||
<record id="sla_resolution" model="project.sla">
|
||||
<field name="name">Standard Resolution Time</field>
|
||||
<field name="control_model">project.issue</field>
|
||||
<field name="control_field_id"
|
||||
ref="project_issue.field_project_issue_date_closed"/>
|
||||
</record>
|
||||
<record id="sla_resolution_rule1" model="project.sla.line">
|
||||
<field name="sla_id" ref="sla_resolution"/>
|
||||
<field name="sequence">10</field>
|
||||
<field name="name">Resolution in two business days</field>
|
||||
<field name="condition">obj.priority <= '2'</field>
|
||||
<field name="limit_qty">16</field>
|
||||
<field name="warn_qty">8</field>
|
||||
</record>
|
||||
<record id="sla_resolution_rule2" model="project.sla.line">
|
||||
<field name="sla_id" ref="sla_resolution"/>
|
||||
<field name="sequence">20</field>
|
||||
<field name="name">Resolution in three business days</field>
|
||||
<field name="condition"></field>
|
||||
<field name="limit_qty">24</field>
|
||||
<field name="warn_qty">16</field>
|
||||
</record>
|
||||
|
||||
<record id="sla_response" model="project.sla">
|
||||
<field name="name">Standard Response Time</field>
|
||||
<field name="control_model">project.issue</field>
|
||||
<field name="control_field_id"
|
||||
ref="project_issue.field_project_issue_date_open"/>
|
||||
</record>
|
||||
<record id="sla_response_rule1" model="project.sla.line">
|
||||
<field name="sla_id" ref="sla_response"/>
|
||||
<field name="sequence">10</field>
|
||||
<field name="name">Response in one business day</field>
|
||||
<field name="condition">obj.priority <= '2'</field>
|
||||
<field name="limit_qty">8</field>
|
||||
<field name="warn_qty">4</field>
|
||||
</record>
|
||||
<record id="sla_response_rule2" model="project.sla.line">
|
||||
<field name="sla_id" ref="sla_response"/>
|
||||
<field name="sequence">20</field>
|
||||
<field name="name">Response in two business days</field>
|
||||
<field name="condition"></field>
|
||||
<field name="limit_qty">16</field>
|
||||
<field name="warn_qty">8</field>
|
||||
</record>
|
||||
|
||||
<!-- Set Contract Resolution SLA Definition -->
|
||||
<record id="project.project_project_1_account_analytic_account" model="account.analytic.account">
|
||||
<field name="sla_ids" eval="[(6, 0, [ref('sla_resolution')])]" />
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
48
project_sla/project_sla_view.xml
Normal file
48
project_sla/project_sla_view.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="view_sla_lines_tree" model="ir.ui.view">
|
||||
<field name="name">view_sla_lines_tree</field>
|
||||
<field name="model">project.sla.line</field>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<tree string="Definitions">
|
||||
<field name="sequence"/>
|
||||
<field name="name"/>
|
||||
<field name="condition"/>
|
||||
<field name="limit_qty"/>
|
||||
<field name="warn_qty"/>
|
||||
</tree>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_sla_form" model="ir.ui.view">
|
||||
<field name="name">view_sla_form</field>
|
||||
<field name="model">project.sla</field>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<form string="SLA Definition">
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
<field name="control_model"/>
|
||||
<field name="control_field_id"/>
|
||||
<notebook colspan="4">
|
||||
<page string="Rules" name="rules_page">
|
||||
<field name="sla_line_ids" nolabel="1"/>
|
||||
</page>
|
||||
<page string="Contracts" name="contracts_page">
|
||||
<field name="analytic_ids" nolabel="1" />
|
||||
</page>
|
||||
</notebook>
|
||||
<button name="reapply_slas" colspan="2"
|
||||
string="Reapply SLA on Contracts"
|
||||
type="object" />
|
||||
</form>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
20
project_sla/project_view.xml
Normal file
20
project_sla/project_view.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="edit_project_sla" model="ir.ui.view">
|
||||
<field name="name">edit_project_sla</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="project.edit_project"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<!-- make resource calendar always visible -->
|
||||
<group string="Administration" position="attributes">
|
||||
<attribute name="groups"/>
|
||||
</group>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
8
project_sla/security/ir.model.access.csv
Normal file
8
project_sla/security/ir.model.access.csv
Normal file
@@ -0,0 +1,8 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_sla_manager,access_sla_manager,model_project_sla,project.group_project_manager,1,1,1,1
|
||||
access_sla_user,access_sla_user,model_project_sla,base.group_user,1,0,0,0
|
||||
access_sla_lines_manager,access_sla_lines_manager,model_project_sla_line,project.group_project_manager,1,1,1,1
|
||||
access_sla_lines_user,access_sla_lines_user,model_project_sla_line,base.group_user,1,0,0,0
|
||||
access_sla_control_manager,access_sla_control_manager,model_project_sla_control,project.group_project_manager,1,1,0,0
|
||||
access_sla_control_user,access_sla_control_user,model_project_sla_control,base.group_user,1,0,0,0
|
||||
access_sla_controlled_manager,access_sla_controlled_manager,model_project_sla_controlled,project.group_project_manager,1,1,1,1
|
||||
|
BIN
project_sla/static/src/img/icon.png
Normal file
BIN
project_sla/static/src/img/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
66
project_sla/test/project_sla.yml
Normal file
66
project_sla/test/project_sla.yml
Normal file
@@ -0,0 +1,66 @@
|
||||
-
|
||||
Cleanup previous test run
|
||||
-
|
||||
!python {model: project.issue}: |
|
||||
res = self.search(cr, uid, [('name', '=', 'My monitor is flickering')])
|
||||
self.unlink(cr, uid, res)
|
||||
-
|
||||
Create a new Issue
|
||||
-
|
||||
!record {model: project.issue, id: issue1, view: False}:
|
||||
name: "My monitor is flickering"
|
||||
project_id: project.project_project_1
|
||||
priority: "3"
|
||||
user_id: base.user_root
|
||||
partner_id: base.res_partner_2
|
||||
email_from: agr@agrolait.com
|
||||
categ_ids:
|
||||
- project_issue.project_issue_category_01
|
||||
-
|
||||
Close the Issue
|
||||
-
|
||||
!python {model: project.issue}: |
|
||||
self.case_close(cr, uid, [ref("issue1")])
|
||||
-
|
||||
Force the Issue's Create Date and Close Date
|
||||
Created friday before opening hour, closed on next monday near closing hour
|
||||
-
|
||||
!python {model: project.issue}: |
|
||||
import time
|
||||
self.write(cr, uid, [ref("issue1"),], {
|
||||
'create_date': time.strftime('2013-11-22 06:15:00'),
|
||||
'date_closed': time.strftime('2013-11-25 16:45:00'),
|
||||
})
|
||||
-
|
||||
There should be Service Level info generated on the Issue
|
||||
-
|
||||
!assert {model: project.issue, id: issue1, string: Issue should have calculated service levels}:
|
||||
- len(sla_control_ids) == 2
|
||||
-
|
||||
Assign an additional "Response SLA" to the Contract
|
||||
-
|
||||
!python {model: account.analytic.account}: |
|
||||
self.write(cr, uid, [ref('project.project_project_1_account_analytic_account')],
|
||||
{'sla_ids': [(4, ref('sla_response'))]})
|
||||
-
|
||||
Button to Reapply the SLA Definition
|
||||
-
|
||||
!python {model: project.sla}: |
|
||||
self._reapply_slas(cr, uid, [ref('sla_resolution')], recalc_closed=True)
|
||||
-
|
||||
There should be two Service Level lines generated on the Issue
|
||||
-
|
||||
!assert {model: project.issue, id: issue1, string: Issue should have two calculated service levels}:
|
||||
- len(sla_control_ids) == 2
|
||||
-
|
||||
The Issue's Resolution SLA should be "3 business days"
|
||||
-
|
||||
!python {model: project.issue}: |
|
||||
issue = self.browse(cr, uid, ref('issue1'))
|
||||
for x in issue.sla_control_ids:
|
||||
print x.sla_line_id.name
|
||||
if x.sla_line_id.id == ref("sla_resolution_rule2"):
|
||||
assert x.sla_achieved == 1, "Issue resolution SLA should be achieved"
|
||||
break
|
||||
else:
|
||||
assert False, 'Issue Resolution SLA should be "3 business days"'
|
||||
Reference in New Issue
Block a user