Merge lp:~dreis-pt/contract-management/7.0-project_sla-dr into lp:~contract-management-core-editors/contract-management/7.0

Proposed by Daniel Reis
Status: Merged
Approved by: Guewen Baconnier @ Camptocamp
Approved revision: 10
Merged at revision: 10
Proposed branch: lp:~dreis-pt/contract-management/7.0-project_sla-dr
Merge into: lp:~contract-management-core-editors/contract-management/7.0
Diff against target: 1521 lines (+1421/-0)
17 files modified
project_sla/__init__.py (+5/-0)
project_sla/__openerp__.py (+132/-0)
project_sla/analytic_account.py (+69/-0)
project_sla/analytic_account_view.xml (+24/-0)
project_sla/i18n/project_sla.pot (+296/-0)
project_sla/m2m.py (+75/-0)
project_sla/project_issue.py (+29/-0)
project_sla/project_issue_view.xml (+60/-0)
project_sla/project_sla.py (+86/-0)
project_sla/project_sla_control.py (+322/-0)
project_sla/project_sla_control_data.xml (+18/-0)
project_sla/project_sla_control_view.xml (+25/-0)
project_sla/project_sla_demo.xml (+138/-0)
project_sla/project_sla_view.xml (+48/-0)
project_sla/project_view.xml (+20/-0)
project_sla/security/ir.model.access.csv (+8/-0)
project_sla/test/project_sla.yml (+66/-0)
To merge this branch: bzr merge lp:~dreis-pt/contract-management/7.0-project_sla-dr
Reviewer Review Type Date Requested Status
Sandy Carter (http://www.savoirfairelinux.com) code review Approve
Joël Grand-Guillaume @ camptocamp code review, no tests Approve
Review via email: mp+199645@code.launchpad.net

Description of the change

To post a comment you must log in.
Revision history for this message
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote :

Hi Daniel,

Thanks for submitting that here. A little tips: did you know you can click on "Resubmit Proposal" on the top right corner of an existing MP and change the source and destination branch ?

Using this, you conserve the historic of the discussion tha took place on the previous MP, even if source and destination branch are different. Very useful when you set a wrong destination branch or a wrong source branch or both !

Otherwise, LGTM, Thanks you.

review: Approve (code review, no tests)
Revision history for this message
Daniel Reis (dreis-pt) wrote :

Good to know that, thanks.

Revision history for this message
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote :
Download full text (3.4 KiB)

PEP8 issues:
project_sla/analytic_account.py:32:9: E123 closing bracket does not match indentation of opening bracket's line
project_sla/project_sla.py:41:40: E241 multiple spaces after ','
project_sla/project_sla.py:42:9: E123 closing bracket does not match indentation of opening bracket's line
project_sla/project_sla.py:45:9: E123 closing bracket does not match indentation of opening bracket's line
project_sla/project_sla.py:81:9: E123 closing bracket does not match indentation of opening bracket's line
project_sla.py:84:9: E123 closing bracket does not match indentation of opening bracket's line
project_sla/project_sla_control.py:83:9: E123 closing bracket does not match indentation of opening bracket's line
project_sla/project_sla_control.py:149:37: E241 multiple spaces after ','
project_sla/project_sla_control.py:298:9: E123 closing bracket does not match indentation of opening bracket's line

Code issues:
project_sla/m2m.py:58:12: redundant parenthesis, if you want to make it a tuple, it should be [(5, )]
project_sla/m2m.py:73:23: redundant parenthesis, if you want to make it a tuple, it should be [(5, )]
project_sla/analytic_account.py:34:5: context is None not handled (if context is None: context = dict())
project_sla/analytic_account.py:69:5: context is None not handled (if context is None: context = dict())
project_sla/project_sla.py:47:5: context is None not handled (if context is None: context = dict())
project_sla/project_sla.py:60:5: context is None not handled (if context is None: context = dict())
project_sla/project_sla_control.py:85:5: context is None not handled (if context is None: context = dict())
project_sla/project_sla_control.py:104:5: context is None not handled (if context is None: context = dict())
project_sla/project_sla_control.py:127:5: context is None not handled (if context is None: context = dict())
project_sla/project_sla_control.py:164:5: context is None not handled (if context is None: context = dict())
project_sla/project_sla_control.py:300:5: context is None not handled (if context is None: context = dict())
project_sla/project_sla_control.py:307:5: context is None not handled (if context is None: context = dict())
project_sla/project_sla_control.py:317:5: context is None not handled (if context is None: context = dict())

Minor code issues:
project_sla/project_sla_control.py:112:15: would have gone with: now = dt.now().strftime(DT_FMT)
project_sla/project_issue_view.xml:60:1: Commented out code
project_sla/project_sla_control.py:104:17: Xml Tag Has Empty Body
project_sla/project_sla_control.py:104:17: Xml Tag Has Empty Body
project_sla/analytic_account.py:23:1: _logger declared but never used
project_sla/project_sla.py:24:1: no _description
project_sla/project_sla.py:65:1: no _description
project_sla/project_sla_control.py:65:20: _description not translated
project_sla/project_sla_control.py:292:20: _description not translated

Spelling:
project_sla/project_view.xml:6:37: projec -> project
project_sla/project_issue_view.xml:32:56: slak -> sla
project_sla/__openerp__.py:41:43: easilly -> easily
project_sla/project_issue_view.xml:81:19: recomputations -> re-computations
project_sla/project_sla_control.p...

Read more...

review: Needs Fixing (code review, no test)
10. By Daniel Reis

Minor fixes

Revision history for this message
Daniel Reis (dreis-pt) wrote :

Thanks for the thorough review, Sandy.
I just pushed the fixes, and here go some comments on it:

> PEP8 issues:

You just made me realize my pep8 configs needed some tuning.
E123 is not strict PEP8 (see E123 on https://pep8.readthedocs.org/en/latest/intro.html), and I choose not to follow it: I feel that it makes you add unnecessary indentation or extra lines.
The E241 were fixed.

> Code issues:
> project_sla/m2m.py:58:12: redundant parenthesis, if you want to make it a
> tuple, it should be [(5, )]

You are right. I ended up using [(5, 0)] because I found code in the standard addons using it like that.

> context is None not handled (if context is None: context = dict())

If context is not manipulated and is only passed through, it's unnecessary to handle the context == None case. So, I didn't fix these.

> Minor code issues:
> project_sla/project_sla_control.py:112:15: would have gone with: now =
> dt.now().strftime(DT_FMT)
> project_sla/project_issue_view.xml:60:1: Commented out code
> project_sla/project_sla.py:24:1: no _description
> project_sla/project_sla.py:65:1: no _description
> project_sla/analytic_account.py:23:1: _logger declared but never used

Yes; fixed.

> project_sla/project_sla_control.py:104:17: Xml Tag Has Empty Body
> project_sla/project_sla_control.py:104:17: Xml Tag Has Empty Body
> project_sla/project_sla_control.py:65:20: _description not translated
> project_sla/project_sla_control.py:292:20: _description not translated

Hmm, didn't find XML tags on those .py files, and I found the translation strings in the .pot file. Can you check this again?

> Spelling:

Fixed.

> Out of curiosity, I noticed that you're using CamelCase for your class names,
> is there a reason for that?

I'm using it because [PEP8](http://www.python.org/dev/peps/pep-0008/#class-names) states that "class names should normally use the CapWords convention", and I don't any reason to not follow it.

Revision history for this message
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote :

> > project_sla/project_sla_control.py:104:17: Xml Tag Has Empty Body
> > project_sla/project_sla_control.py:104:17: Xml Tag Has Empty Body
> > project_sla/project_sla_control.py:65:20: _description not translated
> > project_sla/project_sla_control.py:292:20: _description not translated
>
> Hmm, didn't find XML tags on those .py files, and I found the translation
> strings in the .pot file. Can you check this again?
>
Sorry, copy Paste error.
project_sla/project_sla_demo.xml:104:17: Xml Tag Has Empty Body
project_sla/project_sla_demo.xml:127:17: Xml Tag Has Empty Body

As for translating _description, if it's not done here (_description = _('...')), then it won't be translated in python code (ex: mail_thread widget -- though that one has a soon-to-be-fixed bug too https://bugs.launchpad.net/openobject-addons/+bug/1262000).
Essentially if you invoke _(self.pool.get('project.sla')._description), it will not check the pot file because it doesn't include the following for 'SLA Definition':
#, python-format

> > Out of curiosity, I noticed that you're using CamelCase for your class
> names,
> > is there a reason for that?
>
> I'm using it because [PEP8](http://www.python.org/dev/peps/pep-0008/#class-
> names) states that "class names should normally use the CapWords convention",
> and I don't any reason to not follow it.

Glad to hear that. I think I'll start using that convention too.

Revision history for this message
Daniel Reis (dreis-pt) wrote :

> project_sla/project_sla_demo.xml:104:17: Xml Tag Has Empty Body
> project_sla/project_sla_demo.xml:127:17: Xml Tag Has Empty Body

I can see that it could be neater, but strictly speaking this neither a programming nor a style issue, so I think it isn't worth the trouble.

> As for translating _description, if it's not done here (_description =
> _('...')), then it won't be translated in python code (ex: mail_thread widget
> -- though that one has a soon-to-be-fixed bug too https://bugs.launchpad.net
> /openobject-addons/+bug/1262000).

I can't find a similar example in the official addons; it seems to me that it's a temporary workaround for a bug. I would prefer to not use it unless proven to be absolutely necessary.

Revision history for this message
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote :

LGTM

review: Approve (code review)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'project_sla'
=== added file 'project_sla/__init__.py'
--- project_sla/__init__.py 1970-01-01 00:00:00 +0000
+++ project_sla/__init__.py 2013-12-26 15:27:30 +0000
@@ -0,0 +1,5 @@
1# -*- coding: utf-8 -*-
2import project_sla
3import analytic_account
4import project_sla_control
5import project_issue
06
=== added file 'project_sla/__openerp__.py'
--- project_sla/__openerp__.py 1970-01-01 00:00:00 +0000
+++ project_sla/__openerp__.py 2013-12-26 15:27:30 +0000
@@ -0,0 +1,132 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright (C) 2013 Daniel Reis
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU Affero General Public License as
8# published by the Free Software Foundation, either version 3 of the
9# License, or (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU Affero General Public License for more details.
15#
16# You should have received a copy of the GNU Affero General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19##############################################################################
20{
21 'name': 'Service Level Agreements',
22 'summary': 'Define SLAs for your Contracts',
23 'version': '1.0',
24 "category": "Project Management",
25 'description': """\
26Contract SLAs
27===============
28
29SLAs are assigned to Contracts, on the Analytic Account form, SLA Definition
30separator. This is also where new SLA Definitions are created.
31
32One Contract can have several SLA Definitions attached, allowing for
33"composite SLAs". For example, a contract could have a Response Time SLA (time
34to start resolution) and a Resolution Time SLA (time to close request).
35
36
37SLA Controlled Documents
38========================
39
40Only Project Issue documents are made SLA controllable.
41However, a framework is made available to easily build extensions to make
42other documents models SLA controlled.
43
44SLA controlled documents have attached information on the list of SLA rules
45they should meet (more than one in the case for composite SLAs) and a summary
46SLA status:
47
48 * "watching" the service level (it has SLA requirements to meet)
49 * under "warning" (limit dates are close, special attention is needed)
50 * "failed" (one on the SLA limits has not been met)
51 * "achieved" (all SLA limits have been met)
52
53Transient states, such as "watching" and "warning", are regularly updated by
54a hourly scheduled job, that reevaluates the warning and limit dates against
55the current time and changes the state when find dates that have been exceeded.
56
57To decide what SLA Definitions apply for a specific document, first a lookup
58is made for a ``analytic_account_id`` field. If not found, then it will
59look up for the ``project_id`` and it's corresponding ``analytic_account_id``.
60
61Specifically, the Service Desk module introduces a Analytic Account field for
62Project Issues. This makes it possible for a Service Team (a "Project") to
63have a generic SLA, but at the same time allow for some Contracts to have
64specific SLAs (such as the case for "premium" service conditions).
65
66
67SLA Definitions and Rules
68=========================
69
70New SLA Definitions are created from the Analytic Account form, SLA Definition
71field.
72
73Each definition can have one or more Rules.
74The particular rule to use is decided by conditions, so that you can set
75different service levels based on request attributes, such as Priority or
76Category.
77Each rule condition is evaluated in "sequence" order, and the first onea to met
78is the one to be used.
79In the simplest case, a single rule with no condition is just what is needed.
80
81Each rule sets a number of hours until the "limit date", and the number of
82hours until a "warning date". The former will be used to decide if the SLA
83was achieved, and the later can be used for automatic alarms or escalation
84procedures.
85
86Time will be counted from creation date, until the "Control Date" specified for
87the SLA Definition. That would usually be the "Close" (time until resolution)
88or the "Open" (time until response) dates.
89
90The working calendar set in the related Project definitions will be used (see
91the "Other Info" tab). If none is defined, a builtin "all days, 8-12 13-17"
92default calendar is used.
93
94A timezone and leave calendars will also used, based on either the assigned
95user (document's `user_id`) or on the current user.
96
97
98Setup checklist
99===============
100
101The basic steps to configure SLAs for a Project are:
102
103 * Set Project's Working Calendar, at Project definitions, "Other Info" tab
104 * Go to the Project's Analytic Account form; create and set SLA Definitions
105 * Use the "Reapply SLAs" button on the Analytic Account form
106 * See Project Issue's calculated SLAs in the new "Service Levels" tab
107
108
109Credits and Contributors
110========================
111
112 * Daniel Reis (https://launchpad.net/~dreis-pt)
113 * David Vignoni, author of the icon from the KDE 3.x Nuvola icon theme
114""",
115 'author': 'Daniel Reis',
116 'website': '',
117 'depends': [
118 'project_issue',
119 ],
120 'data': [
121 'project_sla_view.xml',
122 'project_sla_control_view.xml',
123 'project_sla_control_data.xml',
124 'analytic_account_view.xml',
125 'project_view.xml',
126 'project_issue_view.xml',
127 'security/ir.model.access.csv',
128 ],
129 'demo': ['project_sla_demo.xml'],
130 'test': ['test/project_sla.yml'],
131 'installable': True,
132}
0133
=== added file 'project_sla/analytic_account.py'
--- project_sla/analytic_account.py 1970-01-01 00:00:00 +0000
+++ project_sla/analytic_account.py 2013-12-26 15:27:30 +0000
@@ -0,0 +1,69 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright (C) 2013 Daniel Reis
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU Affero General Public License as
8# published by the Free Software Foundation, either version 3 of the
9# License, or (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU Affero General Public License for more details.
15#
16# You should have received a copy of the GNU Affero General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19##############################################################################
20
21from openerp.osv import fields, orm
22
23
24class AnalyticAccount(orm.Model):
25 """ Add SLA to Analytic Accounts """
26 _inherit = 'account.analytic.account'
27 _columns = {
28 'sla_ids': fields.many2many(
29 'project.sla', string='Service Level Agreement'),
30 }
31
32 def _reapply_sla(self, cr, uid, ids, recalc_closed=False, context=None):
33 """
34 Force SLA recalculation on open documents that already are subject to
35 this SLA Definition.
36 To use after changing a Contract SLA or it's Definitions.
37 The ``recalc_closed`` flag allows to also recompute closed documents.
38 """
39 ctrl_obj = self.pool.get('project.sla.control')
40 proj_obj = self.pool.get('project.project')
41 exclude_states = ['cancelled'] + (not recalc_closed and ['done'] or [])
42 for contract in self.browse(cr, uid, ids, context=context):
43 # for each contract, and for each model under SLA control ...
44 for m_name in set([sla.control_model for sla in contract.sla_ids]):
45 model = self.pool.get(m_name)
46 doc_ids = []
47 if 'analytic_account_id' in model._columns:
48 doc_ids += model.search(
49 cr, uid,
50 [('analytic_account_id', '=', contract.id),
51 ('state', 'not in', exclude_states)],
52 context=context)
53 if 'project_id' in model._columns:
54 proj_ids = proj_obj.search(
55 cr, uid, [('analytic_account_id', '=', contract.id)],
56 context=context)
57 doc_ids += model.search(
58 cr, uid,
59 [('project_id', 'in', proj_ids),
60 ('state', 'not in', exclude_states)],
61 context=context)
62 if doc_ids:
63 docs = model.browse(cr, uid, doc_ids, context=context)
64 ctrl_obj.store_sla_control(cr, uid, docs, context=context)
65 return True
66
67 def reapply_sla(self, cr, uid, ids, context=None):
68 """ Reapply SLAs button action """
69 return self._reapply_sla(cr, uid, ids, context=context)
070
=== added file 'project_sla/analytic_account_view.xml'
--- project_sla/analytic_account_view.xml 1970-01-01 00:00:00 +0000
+++ project_sla/analytic_account_view.xml 2013-12-26 15:27:30 +0000
@@ -0,0 +1,24 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <record id="view_account_analytic_account_form_sla" model="ir.ui.view">
6 <field name="name">view_account_analytic_account_form_sla</field>
7 <field name="model">account.analytic.account</field>
8 <field name="inherit_id" ref="analytic.view_account_analytic_account_form"/>
9 <field name="arch" type="xml">
10
11 <page name="contract_page" position="after">
12 <page name="sla_page" string="Service Level Agreement">
13 <field name="sla_ids" nolabel="1"/>
14 <button name="reapply_sla" string="Reapply" type="object"
15 help="Reapply the SLAs to all Contract's documents."
16 groups="project.group_project_manager" />
17 </page>
18 </page>
19
20 </field>
21 </record>
22
23 </data>
24</openerp>
025
=== added directory 'project_sla/i18n'
=== added file 'project_sla/i18n/project_sla.pot'
--- project_sla/i18n/project_sla.pot 1970-01-01 00:00:00 +0000
+++ project_sla/i18n/project_sla.pot 2013-12-26 15:27:30 +0000
@@ -0,0 +1,296 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * project_sla
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 7.0\n"
8"Report-Msgid-Bugs-To: \n"
9"POT-Creation-Date: 2013-12-19 10:28+0000\n"
10"PO-Revision-Date: 2013-12-19 10:28+0000\n"
11"Last-Translator: <>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: \n"
16"Plural-Forms: \n"
17
18#. module: project_sla
19#: model:project.sla,name:project_sla.sla_response
20msgid "Standard Response Time"
21msgstr ""
22
23#. module: project_sla
24#: help:project.sla,control_field_id:0
25msgid "Date field used to check if the SLA was achieved."
26msgstr ""
27
28#. module: project_sla
29#: model:ir.model,name:project_sla.model_project_issue
30msgid "Project Issue"
31msgstr ""
32
33#. module: project_sla
34#: model:ir.model,name:project_sla.model_project_sla_control
35msgid "SLA Control Registry"
36msgstr ""
37
38#. module: project_sla
39#: view:project.sla:0
40msgid "Reapply SLA on Contracts"
41msgstr ""
42
43#. module: project_sla
44#: view:project.project:0
45msgid "Administration"
46msgstr ""
47
48#. module: project_sla
49#: view:project.issue:0
50msgid "Priority"
51msgstr ""
52
53#. module: project_sla
54#: selection:project.issue,sla_state:0
55#: selection:project.sla.control,sla_state:0
56#: selection:project.sla.controlled,sla_state:0
57msgid "Failed"
58msgstr ""
59
60#. module: project_sla
61#: field:project.sla.control,sla_warn_date:0
62msgid "Warning Date"
63msgstr ""
64
65#. module: project_sla
66#: field:project.sla.line,warn_qty:0
67msgid "Hours to Warn"
68msgstr ""
69
70#. module: project_sla
71#: view:project.sla.control:0
72msgid "Service Level"
73msgstr ""
74
75#. module: project_sla
76#: selection:project.issue,sla_state:0
77#: selection:project.sla.control,sla_state:0
78#: selection:project.sla.controlled,sla_state:0
79msgid "Watching"
80msgstr ""
81
82#. module: project_sla
83#: view:project.sla:0
84#: field:project.sla,analytic_ids:0
85msgid "Contracts"
86msgstr ""
87
88#. module: project_sla
89#: field:project.sla,name:0
90#: field:project.sla.line,name:0
91msgid "Title"
92msgstr ""
93
94#. module: project_sla
95#: field:project.sla,active:0
96msgid "Active"
97msgstr ""
98
99#. module: project_sla
100#: field:project.issue,sla_control_ids:0
101#: field:project.sla.controlled,sla_control_ids:0
102msgid "SLA Control"
103msgstr ""
104
105#. module: project_sla
106#: field:project.sla.control,sla_achieved:0
107msgid "Achieved?"
108msgstr ""
109
110#. module: project_sla
111#: view:project.issue:0
112#: field:project.issue,sla_state:0
113#: field:project.sla.control,sla_state:0
114#: field:project.sla.controlled,sla_state:0
115msgid "SLA Status"
116msgstr ""
117
118#. module: project_sla
119#: field:project.sla.line,condition:0
120msgid "Condition"
121msgstr ""
122
123#. module: project_sla
124#: field:project.sla,control_model:0
125msgid "For documents"
126msgstr ""
127
128#. module: project_sla
129#: view:project.sla:0
130#: field:project.sla.line,sla_id:0
131msgid "SLA Definition"
132msgstr ""
133
134#. module: project_sla
135#: model:project.sla.line,name:project_sla.sla_response_rule2
136msgid "Response in two business days"
137msgstr ""
138
139#. module: project_sla
140#: selection:project.issue,sla_state:0
141#: selection:project.sla.control,sla_state:0
142#: selection:project.sla.controlled,sla_state:0
143msgid "Will Fail"
144msgstr ""
145
146#. module: project_sla
147#: field:project.sla.control,sla_line_id:0
148msgid "Service Agreement"
149msgstr ""
150
151#. module: project_sla
152#: field:project.sla.control,doc_id:0
153msgid "Document ID"
154msgstr ""
155
156#. module: project_sla
157#: field:project.sla.control,locked:0
158msgid "Recalculation disabled"
159msgstr ""
160
161#. module: project_sla
162#: field:project.sla.control,sla_limit_date:0
163msgid "Limit Date"
164msgstr ""
165
166#. module: project_sla
167#: help:project.sla.line,condition:0
168msgid "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'"
169msgstr ""
170
171#. module: project_sla
172#: model:project.sla.line,name:project_sla.sla_resolution_rule1
173msgid "Resolution in two business days"
174msgstr ""
175
176#. module: project_sla
177#: view:account.analytic.account:0
178msgid "Reapply"
179msgstr ""
180
181#. module: project_sla
182#: field:project.sla.line,limit_qty:0
183msgid "Hours to Limit"
184msgstr ""
185
186#. module: project_sla
187#: field:project.sla,sla_line_ids:0
188#: view:project.sla.line:0
189msgid "Definitions"
190msgstr ""
191
192#. module: project_sla
193#: model:project.sla.line,name:project_sla.sla_resolution_rule2
194msgid "Resolution in three business days"
195msgstr ""
196
197#. module: project_sla
198#: field:project.sla.control,sla_close_date:0
199msgid "Close Date"
200msgstr ""
201
202#. module: project_sla
203#: model:ir.model,name:project_sla.model_project_sla
204msgid "project.sla"
205msgstr ""
206
207#. module: project_sla
208#: field:project.sla,control_field_id:0
209msgid "Control Date"
210msgstr ""
211
212#. module: project_sla
213#: model:project.sla.line,name:project_sla.sla_response_rule1
214msgid "Response in one business day"
215msgstr ""
216
217#. module: project_sla
218#: selection:project.issue,sla_state:0
219#: selection:project.sla.control,sla_state:0
220#: selection:project.sla.controlled,sla_state:0
221msgid "Achieved"
222msgstr ""
223
224#. module: project_sla
225#: view:account.analytic.account:0
226#: field:account.analytic.account,sla_ids:0
227msgid "Service Level Agreement"
228msgstr ""
229
230#. module: project_sla
231#: model:project.sla,name:project_sla.sla_resolution
232msgid "Standard Resolution Time"
233msgstr ""
234
235#. module: project_sla
236#: field:project.sla.line,sequence:0
237msgid "Sequence"
238msgstr ""
239
240#. module: project_sla
241#: view:project.issue:0
242msgid "Service Levels"
243msgstr ""
244
245#. module: project_sla
246#: model:ir.model,name:project_sla.model_account_analytic_account
247msgid "Analytic Account"
248msgstr ""
249
250#. module: project_sla
251#: view:project.sla:0
252msgid "Rules"
253msgstr ""
254
255#. module: project_sla
256#: help:project.sla.control,locked:0
257msgid "Safeguard manual changes from future automatic recomputations."
258msgstr ""
259
260#. module: project_sla
261#: selection:project.issue,sla_state:0
262#: selection:project.sla.control,sla_state:0
263#: selection:project.sla.controlled,sla_state:0
264msgid "Warning"
265msgstr ""
266
267#. module: project_sla
268#: view:project.issue:0
269msgid "Extra Info"
270msgstr ""
271
272#. module: project_sla
273#: field:project.sla.control,doc_model:0
274msgid "Document Model"
275msgstr ""
276
277#. module: project_sla
278#: model:ir.model,name:project_sla.model_project_sla_line
279msgid "project.sla.line"
280msgstr ""
281
282#. module: project_sla
283#: view:account.analytic.account:0
284msgid "Reapply the SLAs to all Contract's documents."
285msgstr ""
286
287#. module: project_sla
288#: field:project.sla.control,sla_start_date:0
289msgid "Start Date"
290msgstr ""
291
292#. module: project_sla
293#: model:ir.model,name:project_sla.model_project_sla_controlled
294msgid "SLA Controlled Document"
295msgstr ""
296
0297
=== added directory 'project_sla/images'
=== added file 'project_sla/images/10_sla_contract.png'
1Binary files project_sla/images/10_sla_contract.png 1970-01-01 00:00:00 +0000 and project_sla/images/10_sla_contract.png 2013-12-26 15:27:30 +0000 differ298Binary files project_sla/images/10_sla_contract.png 1970-01-01 00:00:00 +0000 and project_sla/images/10_sla_contract.png 2013-12-26 15:27:30 +0000 differ
=== added file 'project_sla/images/20_sla_definition.png'
2Binary files project_sla/images/20_sla_definition.png 1970-01-01 00:00:00 +0000 and project_sla/images/20_sla_definition.png 2013-12-26 15:27:30 +0000 differ299Binary files project_sla/images/20_sla_definition.png 1970-01-01 00:00:00 +0000 and project_sla/images/20_sla_definition.png 2013-12-26 15:27:30 +0000 differ
=== added file 'project_sla/images/30_sla_controlled.png'
3Binary files project_sla/images/30_sla_controlled.png 1970-01-01 00:00:00 +0000 and project_sla/images/30_sla_controlled.png 2013-12-26 15:27:30 +0000 differ300Binary files project_sla/images/30_sla_controlled.png 1970-01-01 00:00:00 +0000 and project_sla/images/30_sla_controlled.png 2013-12-26 15:27:30 +0000 differ
=== added file 'project_sla/m2m.py'
--- project_sla/m2m.py 1970-01-01 00:00:00 +0000
+++ project_sla/m2m.py 2013-12-26 15:27:30 +0000
@@ -0,0 +1,75 @@
1"""
2Wrapper for OpenERP's cryptic write conventions for x2many fields.
3
4Example usage:
5
6 import m2m
7 browse_rec.write({'many_ids: m2m.clear())
8 browse_rec.write({'many_ids: m2m.link(99))
9 browse_rec.write({'many_ids: m2m.add({'name': 'Monty'}))
10 browse_rec.write({'many_ids: m2m.replace([98, 99]))
11
12Since returned values are lists, the can be joined using the plus operator:
13
14 browse_rec.write({'many_ids: m2m.clear() + m2m.link(99))
15
16(Source: https://github.com/dreispt/openerp-write2many)
17"""
18
19
20def create(values):
21 """ Create a referenced record """
22 assert isinstance(values, dict)
23 return [(0, 0, values)]
24
25
26def add(values):
27 """ Intuitive alias for create() """
28 return create(values)
29
30
31def write(id, values):
32 """ Write on referenced record """
33 assert isinstance(id, int)
34 assert isinstance(values, dict)
35 return [(1, id, values)]
36
37
38def remove(id):
39 """ Unlink and delete referenced record """
40 assert isinstance(id, int)
41 return [(2, id)]
42
43
44def unlink(id):
45 """ Unlink but do not delete the referenced record """
46 assert isinstance(id, int)
47 return [(3, id)]
48
49
50def link(id):
51 """ Link but do not delete the referenced record """
52 assert isinstance(id, int)
53 return [(4, id)]
54
55
56def clear():
57 """ Unlink all referenced records (doesn't delete them) """
58 return [(5, 0)]
59
60
61def replace(ids):
62 """ Unlink all current records and replace them with a new list """
63 assert isinstance(ids, list)
64 return [(6, 0, ids)]
65
66
67if __name__ == "__main__":
68 # Tests:
69 assert create({'name': 'Monty'}) == [(0, 0, {'name': 'Monty'})]
70 assert write(99, {'name': 'Monty'}) == [(1, 99, {'name': 'Monty'})]
71 assert remove(99) == [(2, 99)]
72 assert unlink(99) == [(3, 99)]
73 assert clear() == [(5, 0)]
74 assert replace([97, 98, 99]) == [(6, 0, [97, 98, 99])]
75 print("Done!")
076
=== added file 'project_sla/project_issue.py'
--- project_sla/project_issue.py 1970-01-01 00:00:00 +0000
+++ project_sla/project_issue.py 2013-12-26 15:27:30 +0000
@@ -0,0 +1,29 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright (C) 2013 Daniel Reis
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU Affero General Public License as
8# published by the Free Software Foundation, either version 3 of the
9# License, or (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU Affero General Public License for more details.
15#
16# You should have received a copy of the GNU Affero General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19##############################################################################
20
21from openerp.osv import orm
22
23
24class ProjectIssue(orm.Model):
25 """
26 Extend Project Issues to be SLA Controlled
27 """
28 _name = 'project.issue'
29 _inherit = ['project.issue', 'project.sla.controlled']
030
=== added file 'project_sla/project_issue_view.xml'
--- project_sla/project_issue_view.xml 1970-01-01 00:00:00 +0000
+++ project_sla/project_issue_view.xml 2013-12-26 15:27:30 +0000
@@ -0,0 +1,60 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <!-- Project Issue Form -->
6 <record id="project_issue_form_view_sla" model="ir.ui.view">
7 <field name="name">project_issue_form_view_sla</field>
8 <field name="model">project.issue</field>
9 <field name="inherit_id" ref="project_issue.project_issue_form_view"/>
10 <field name="arch" type="xml">
11
12 <page string="Extra Info" position="after">
13 <page name="sla_page" string="Service Levels"
14 attrs="{'invisible': [('sla_state', '=', False)]}">
15 <group>
16 <group>
17 <field name="sla_state" />
18 </group>
19 <group>
20 <field name="write_date" />
21 </group>
22 </group>
23 <field name="sla_control_ids"/>
24 </page>
25 </page>
26
27 </field>
28 </record>
29
30 <!-- Project Issue List -->
31 <record model="ir.ui.view" id="project_issue_tree_view_sla">
32 <field name="name">project_issue_tree_view_sla</field>
33 <field name="model">project.issue</field>
34 <field name="inherit_id" ref="project_issue.project_issue_tree_view"/>
35 <field name="arch" type="xml">
36
37 <field name="project_id" position="after">
38 <field name="sla_state"/>
39 </field>
40
41 </field>
42 </record>
43
44
45 <!-- Project Issue Filter -->
46 <record id="view_project_issue_filter_sdesk" model="ir.ui.view">
47 <field name="name">view_project_issue_filter_sdesk</field>
48 <field name="model">project.issue</field>
49 <field name="inherit_id" ref="project_issue.view_project_issue_filter"/>
50 <field name="arch" type="xml">
51
52 <filter string="Priority" position="after">
53 <filter string="SLA Status" context="{'group_by':'sla_state'}" />
54 </filter>
55
56 </field>
57 </record>
58
59 </data>
60</openerp>
061
=== added file 'project_sla/project_sla.py'
--- project_sla/project_sla.py 1970-01-01 00:00:00 +0000
+++ project_sla/project_sla.py 2013-12-26 15:27:30 +0000
@@ -0,0 +1,86 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright (C) 2013 Daniel Reis
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU Affero General Public License as
8# published by the Free Software Foundation, either version 3 of the
9# License, or (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU Affero General Public License for more details.
15#
16# You should have received a copy of the GNU Affero General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19##############################################################################
20
21from openerp.osv import fields, orm
22
23
24class SLADefinition(orm.Model):
25 """
26 SLA Definition
27 """
28 _name = 'project.sla'
29 _description = 'SLA Definition'
30 _columns = {
31 'name': fields.char('Title', size=64, required=True, translate=True),
32 'active': fields.boolean('Active'),
33 'control_model': fields.char('For documents', size=128, required=True),
34 'control_field_id': fields.many2one(
35 'ir.model.fields', 'Control Date', required=True,
36 domain="[('model_id.model', '=', control_model),"
37 " ('ttype', 'in', ['date', 'datetime'])]",
38 help="Date field used to check if the SLA was achieved."),
39 'sla_line_ids': fields.one2many(
40 'project.sla.line', 'sla_id', 'Definitions'),
41 'analytic_ids': fields.many2many(
42 'account.analytic.account', string='Contracts'),
43 }
44 _defaults = {
45 'active': True,
46 }
47
48 def _reapply_slas(self, cr, uid, ids, recalc_closed=False, context=None):
49 """
50 Force SLA recalculation on all _open_ Contracts for the selected SLAs.
51 To use upon SLA Definition modifications.
52 """
53 contract_obj = self.pool.get('account.analytic.account')
54 for sla in self.browse(cr, uid, ids, context=context):
55 contr_ids = [x.id for x in sla.analytic_ids if x.state == 'open']
56 contract_obj._reapply_sla(
57 cr, uid, contr_ids, recalc_closed=recalc_closed,
58 context=context)
59 return True
60
61 def reapply_slas(self, cr, uid, ids, context=None):
62 """ Reapply SLAs button action """
63 return self._reapply_slas(cr, uid, ids, context=context)
64
65
66class SLARules(orm.Model):
67 """
68 SLA Definition Rule Lines
69 """
70 _name = 'project.sla.line'
71 _definition = 'SLA Definition Rule Lines'
72 _order = 'sla_id,sequence'
73 _columns = {
74 'sla_id': fields.many2one('project.sla', 'SLA Definition'),
75 'sequence': fields.integer('Sequence'),
76 'name': fields.char('Title', size=64, required=True, translate=True),
77 'condition': fields.char(
78 'Condition', size=256, help="Apply only if this expression is "
79 "evaluated to True. The document fields can be accessed using "
80 "either o, obj or object. Example: obj.priority <= '2'"),
81 'limit_qty': fields.integer('Hours to Limit'),
82 'warn_qty': fields.integer('Hours to Warn'),
83 }
84 _defaults = {
85 'sequence': 10,
86 }
087
=== added file 'project_sla/project_sla_control.py'
--- project_sla/project_sla_control.py 1970-01-01 00:00:00 +0000
+++ project_sla/project_sla_control.py 2013-12-26 15:27:30 +0000
@@ -0,0 +1,322 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# Copyright (C) 2013 Daniel Reis
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU Affero General Public License as
8# published by the Free Software Foundation, either version 3 of the
9# License, or (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU Affero General Public License for more details.
15#
16# You should have received a copy of the GNU Affero General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19##############################################################################
20
21from openerp.osv import fields, orm
22from openerp.tools.safe_eval import safe_eval
23from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT as DT_FMT
24from openerp import SUPERUSER_ID
25from datetime import timedelta
26from datetime import datetime as dt
27import m2m
28
29import logging
30_logger = logging.getLogger(__name__)
31
32
33SLA_STATES = [('5', 'Failed'), ('4', 'Will Fail'), ('3', 'Warning'),
34 ('2', 'Watching'), ('1', 'Achieved')]
35
36
37def safe_getattr(obj, dotattr, default=False):
38 """
39 Follow an object attribute dot-notation chain to find the leaf value.
40 If any attribute doesn't exist or has no value, just return False.
41 Checks hasattr ahead, to avoid ORM Browse log warnings.
42 """
43 attrs = dotattr.split('.')
44 while attrs:
45 attr = attrs.pop(0)
46 if attr in obj._model._columns:
47 try:
48 obj = getattr(obj, attr)
49 except AttributeError:
50 return default
51 if not obj:
52 return default
53 else:
54 return default
55 return obj
56
57
58class SLAControl(orm.Model):
59 """
60 SLA Control Registry
61 Each controlled document (Issue, Claim, ...) will have a record here.
62 This model concentrates all the logic for Service Level calculation.
63 """
64 _name = 'project.sla.control'
65 _description = 'SLA Control Registry'
66
67 _columns = {
68 'doc_id': fields.integer('Document ID', readonly=True),
69 'doc_model': fields.char('Document Model', size=128, readonly=True),
70 'sla_line_id': fields.many2one(
71 'project.sla.line', 'Service Agreement'),
72 'sla_warn_date': fields.datetime('Warning Date'),
73 'sla_limit_date': fields.datetime('Limit Date'),
74 'sla_start_date': fields.datetime('Start Date'),
75 'sla_close_date': fields.datetime('Close Date'),
76 'sla_achieved': fields.integer('Achieved?'),
77 'sla_state': fields.selection(SLA_STATES, string="SLA Status"),
78 'locked': fields.boolean(
79 'Recalculation disabled',
80 help="Safeguard manual changes from future automatic "
81 "recomputations."),
82 # Future: perfect SLA manual handling
83 }
84
85 def write(self, cr, uid, ids, vals, context=None):
86 """
87 Update the related Document's SLA State when any of the SLA Control
88 lines changes state
89 """
90 res = super(SLAControl, self).write(
91 cr, uid, ids, vals, context=context)
92 new_state = vals.get('sla_state')
93 if new_state:
94 # just update sla_state without recomputing the whole thing
95 context = context or {}
96 context['__sla_stored__'] = 1
97 for sla in self.browse(cr, uid, ids, context=context):
98 doc = self.pool.get(sla.doc_model).browse(
99 cr, uid, sla.doc_id, context=context)
100 if doc.sla_state < new_state:
101 doc.write({'sla_state': new_state})
102 return res
103
104 def update_sla_states(self, cr, uid, context=None):
105 """
106 Updates SLA States, given the current datetime:
107 Only works on "open" sla states (watching, warning and will fail):
108 - exceeded limit date are set to "will fail"
109 - exceeded warning dates are set to "warning"
110 To be used by a scheduled job.
111 """
112 now = dt.now().strftime(DT_FMT)
113 # SLAs to mark as "will fail"
114 control_ids = self.search(
115 cr, uid,
116 [('sla_state', 'in', ['2', '3']), ('sla_limit_date', '<', now)],
117 context=context)
118 self.write(cr, uid, control_ids, {'sla_state': '4'}, context=context)
119 # SLAs to mark as "warning"
120 control_ids = self.search(
121 cr, uid,
122 [('sla_state', 'in', ['2']), ('sla_warn_date', '<', now)],
123 context=context)
124 self.write(cr, uid, control_ids, {'sla_state': '3'}, context=context)
125 return True
126
127 def _compute_sla_date(self, cr, uid, working_hours, res_uid,
128 start_date, hours, context=None):
129 """
130 Return a limit datetime by adding hours to a start_date, honoring
131 a working_time calendar and a resource's (res_uid) timezone and
132 availability (leaves)
133
134 Currently implemented using a binary search using
135 _interval_hours_get() from resource.calendar. This is
136 resource.calendar agnostic, but could be more efficient if
137 implemented based on it's logic.
138
139 Known issue: the end date can be a non-working time; it would be
140 best for it to be the latest working time possible. Example:
141 if working time is 08:00 - 16:00 and start_date is 19:00, the +8h
142 end date will be 19:00 of the next day, and it should rather be
143 16:00 of the next day.
144 """
145 assert isinstance(start_date, dt)
146 assert isinstance(hours, int) and hours >= 0
147
148 cal_obj = self.pool.get('resource.calendar')
149 target, step = hours * 3600, 16 * 3600
150 lo, hi = start_date, start_date
151 while target > 0 and step > 60:
152 hi = lo + timedelta(seconds=step)
153 check = int(3600 * cal_obj._interval_hours_get(
154 cr, uid, working_hours, lo, hi,
155 timezone_from_uid=res_uid, exclude_leaves=False,
156 context=context))
157 if check <= target:
158 target -= check
159 lo = hi
160 else:
161 step = int(step / 4.0)
162 return hi
163
164 def _get_computed_slas(self, cr, uid, doc, context=None):
165 """
166 Returns a dict with the computed data for SLAs, given a browse record
167 for the target document.
168
169 * The SLA used is either from a related analytic_account_id or
170 project_id, whatever is found first.
171 * The work calendar is taken from the Project's definitions ("Other
172 Info" tab -> Working Time).
173 * The timezone used for the working time calculations are from the
174 document's responsible User (user_id) or from the current User (uid).
175
176 For the SLA Achieved calculation:
177
178 * Creation date is used to start counting time
179 * Control date, used to calculate SLA achievement, is defined in the
180 SLA Definition rules.
181 """
182 def datetime2str(dt_value, fmt): # tolerant datetime to string
183 return dt_value and dt.strftime(dt_value, fmt) or None
184
185 res = []
186 sla_ids = (safe_getattr(doc, 'analytic_account_id.sla_ids') or
187 safe_getattr(doc, 'project_id.analytic_account_id.sla_ids'))
188 if not sla_ids:
189 return res
190
191 for sla in sla_ids:
192 if sla.control_model != doc._table_name:
193 continue # SLA not for this model; skip
194
195 for l in sla.sla_line_ids:
196 eval_context = {'o': doc, 'obj': doc, 'object': doc}
197 if not l.condition or safe_eval(l.condition, eval_context):
198 start_date = dt.strptime(doc.create_date, DT_FMT)
199 res_uid = doc.user_id.id or uid
200 cal = safe_getattr(
201 doc, 'project_id.resource_calendar_id.id')
202 warn_date = self._compute_sla_date(
203 cr, uid, cal, res_uid, start_date, l.warn_qty,
204 context=context)
205 lim_date = self._compute_sla_date(
206 cr, uid, cal, res_uid, warn_date,
207 l.limit_qty - l.warn_qty,
208 context=context)
209 # evaluate sla state
210 control_val = getattr(doc, sla.control_field_id.name)
211 if control_val:
212 control_date = dt.strptime(control_val, DT_FMT)
213 if control_date > lim_date:
214 sla_val, sla_state = 0, '5' # failed
215 else:
216 sla_val, sla_state = 1, '1' # achieved
217 else:
218 control_date = None
219 now = dt.now()
220 if now > lim_date:
221 sla_val, sla_state = 0, '4' # will fail
222 elif now > warn_date:
223 sla_val, sla_state = 0, '3' # warning
224 else:
225 sla_val, sla_state = 0, '2' # watching
226
227 res.append(
228 {'sla_line_id': l.id,
229 'sla_achieved': sla_val,
230 'sla_state': sla_state,
231 'sla_warn_date': datetime2str(warn_date, DT_FMT),
232 'sla_limit_date': datetime2str(lim_date, DT_FMT),
233 'sla_start_date': datetime2str(start_date, DT_FMT),
234 'sla_close_date': datetime2str(control_date, DT_FMT),
235 'doc_id': doc.id,
236 'doc_model': sla.control_model})
237 break
238
239 if sla_ids and not res:
240 _logger.warning("No valid SLA rule found for %d, SLA Ids %s"
241 % (doc.id, repr([x.id for x in sla_ids])))
242 return res
243
244 def store_sla_control(self, cr, uid, docs, context=None):
245 """
246 Used by controlled documents to ask for SLA calculation and storage.
247 ``docs`` is a Browse object
248 """
249 # context flag to avoid infinite loops on further writes
250 context = context or {}
251 if '__sla_stored__' in context:
252 return False
253 else:
254 context['__sla_stored__'] = 1
255
256 res = []
257 for ix, doc in enumerate(docs):
258 if ix and ix % 50 == 0:
259 _logger.info('...%d SLAs recomputed for %s' % (ix, doc._name))
260 control = {x.sla_line_id.id: x
261 for x in doc.sla_control_ids}
262 sla_recs = self._get_computed_slas(cr, uid, doc, context=context)
263 # calc sla control lines
264 if sla_recs:
265 slas = []
266 for sla_rec in sla_recs:
267 sla_line_id = sla_rec.get('sla_line_id')
268 if sla_line_id in control:
269 control_rec = control.get(sla_line_id)
270 if not control_rec.locked:
271 slas += m2m.write(control_rec.id, sla_rec)
272 else:
273 slas += m2m.add(sla_rec)
274 else:
275 slas = m2m.clear()
276 # calc sla control summary
277 vals = {'sla_state': None, 'sla_control_ids': slas}
278 if sla_recs and doc.sla_control_ids:
279 vals['sla_state'] = max(
280 x.sla_state for x in doc.sla_control_ids)
281 # store sla
282 doc._model.write( # regular users can't write on SLA Control
283 cr, SUPERUSER_ID, [doc.id], vals, context=context)
284 return res
285
286
287class SLAControlled(orm.AbstractModel):
288 """
289 SLA Controlled documents: AbstractModel to apply SLA control on Models
290 """
291 _name = 'project.sla.controlled'
292 _description = 'SLA Controlled Document'
293 _columns = {
294 'sla_control_ids': fields.many2many(
295 'project.sla.control', string="SLA Control", ondelete='cascade'),
296 'sla_state': fields.selection(
297 SLA_STATES, string="SLA Status", readonly=True),
298 }
299
300 def create(self, cr, uid, vals, context=None):
301 res = super(SLAControlled, self).create(cr, uid, vals, context=context)
302 docs = self.browse(cr, uid, [res], context=context)
303 self.pool.get('project.sla.control').store_sla_control(
304 cr, uid, docs, context=context)
305 return res
306
307 def write(self, cr, uid, ids, vals, context=None):
308 res = super(SLAControlled, self).write(
309 cr, uid, ids, vals, context=context)
310 docs = [x for x in self.browse(cr, uid, ids, context=context)
311 if (x.state != 'cancelled') and
312 (x.state != 'done' or x.sla_state not in ['1', '5'])]
313 self.pool.get('project.sla.control').store_sla_control(
314 cr, uid, docs, context=context)
315 return res
316
317 def unlink(self, cr, uid, ids, context=None):
318 # Unlink and delete all related Control records
319 for doc in self.browse(cr, uid, ids, context=context):
320 vals = [m2m.remove(x.id)[0] for x in doc.sla_control_ids]
321 doc.write({'sla_control_ids': vals})
322 return super(SLAControlled, self).unlink(cr, uid, ids, context=context)
0323
=== added file 'project_sla/project_sla_control_data.xml'
--- project_sla/project_sla_control_data.xml 1970-01-01 00:00:00 +0000
+++ project_sla/project_sla_control_data.xml 2013-12-26 15:27:30 +0000
@@ -0,0 +1,18 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data noupdate="1">
4
5 <record id="ir_cron_sla_action" model="ir.cron">
6 <field name="name">Update SLA States</field>
7 <field name="priority" eval="100"/>
8 <field name="interval_number">1</field>
9 <field name="interval_type">hours</field>
10 <field name="numbercall">-1</field>
11 <field name="doall" eval="False"/>
12 <field name="model">project.sla.control</field>
13 <field name="function">update_sla_states</field>
14 <field name="args">()</field>
15 </record>
16
17 </data>
18</openerp>
019
=== added file 'project_sla/project_sla_control_view.xml'
--- project_sla/project_sla_control_view.xml 1970-01-01 00:00:00 +0000
+++ project_sla/project_sla_control_view.xml 2013-12-26 15:27:30 +0000
@@ -0,0 +1,25 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <!-- List view used when the sla_control_ids field
6 is added to controlled document's form -->
7 <record id="view_sla_control_tree" model="ir.ui.view">
8 <field name="name">view_sla_control_tree</field>
9 <field name="model">project.sla.control</field>
10 <field name="arch" type="xml">
11
12 <tree string="Service Level">
13 <field name="sla_line_id"/>
14 <field name="sla_state"/>
15 <field name="sla_start_date"/>
16 <field name="sla_warn_date"/>
17 <field name="sla_limit_date"/>
18 <field name="sla_close_date"/>
19 </tree>
20
21 </field>
22 </record>
23
24 </data>
25</openerp>
026
=== added file 'project_sla/project_sla_demo.xml'
--- project_sla/project_sla_demo.xml 1970-01-01 00:00:00 +0000
+++ project_sla/project_sla_demo.xml 2013-12-26 15:27:30 +0000
@@ -0,0 +1,138 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <!-- Working Time calendar -->
6 <record id="worktime_9_18" model="resource.calendar">
7 <field name="name">Working Days 09-13 14-18</field>
8 </record>
9 <record id="worktime 9_18_0M" model="resource.calendar.attendance">
10 <field name="dayofweek">0</field>
11 <field name="name">Monday Morning</field>
12 <field name="hour_from">9</field>
13 <field name="hour_to">13</field>
14 <field name="calendar_id" ref="worktime_9_18" />
15 </record>
16 <record id="worktime 9_18_0A" model="resource.calendar.attendance">
17 <field name="dayofweek">0</field>
18 <field name="name">Monday Afternoon</field>
19 <field name="hour_from">14</field>
20 <field name="hour_to">18</field>
21 <field name="calendar_id" ref="worktime_9_18" />
22 </record>
23 <record id="worktime 9_18_1M" model="resource.calendar.attendance">
24 <field name="dayofweek">1</field>
25 <field name="name">Tuesday Morning</field>
26 <field name="hour_from">9</field>
27 <field name="hour_to">13</field>
28 <field name="calendar_id" ref="worktime_9_18" />
29 </record>
30 <record id="worktime 9_18_1A" model="resource.calendar.attendance">
31 <field name="dayofweek">1</field>
32 <field name="name">Tuesday Afternoon</field>
33 <field name="hour_from">14</field>
34 <field name="hour_to">18</field>
35 <field name="calendar_id" ref="worktime_9_18" />
36 </record>
37 <record id="worktime 9_18_2M" model="resource.calendar.attendance">
38 <field name="dayofweek">2</field>
39 <field name="name">Wednesday Morning</field>
40 <field name="hour_from">9</field>
41 <field name="hour_to">13</field>
42 <field name="calendar_id" ref="worktime_9_18" />
43 </record>
44 <record id="worktime 9_18_2A" model="resource.calendar.attendance">
45 <field name="dayofweek">2</field>
46 <field name="name">Wednesday Afternoon</field>
47 <field name="hour_from">14</field>
48 <field name="hour_to">18</field>
49 <field name="calendar_id" ref="worktime_9_18" />
50 </record>
51 <record id="worktime 9_18_3M" model="resource.calendar.attendance">
52 <field name="dayofweek">3</field>
53 <field name="name">Thursday Morning</field>
54 <field name="hour_from">9</field>
55 <field name="hour_to">13</field>
56 <field name="calendar_id" ref="worktime_9_18" />
57 </record>
58 <record id="worktime 9_18_3A" model="resource.calendar.attendance">
59 <field name="dayofweek">3</field>
60 <field name="name">Thursday Afternoon</field>
61 <field name="hour_from">14</field>
62 <field name="hour_to">18</field>
63 <field name="calendar_id" ref="worktime_9_18" />
64 </record>
65 <record id="worktime 9_18_4M" model="resource.calendar.attendance">
66 <field name="dayofweek">4</field>
67 <field name="name">Friday Morning</field>
68 <field name="hour_from">9</field>
69 <field name="hour_to">13</field>
70 <field name="calendar_id" ref="worktime_9_18" />
71 </record>
72 <record id="worktime 9_18_4A" model="resource.calendar.attendance">
73 <field name="dayofweek">4</field>
74 <field name="name">Friday Afternoon</field>
75 <field name="hour_from">14</field>
76 <field name="hour_to">18</field>
77 <field name="calendar_id" ref="worktime_9_18" />
78 </record>
79
80 <!-- Set Project Calendar -->
81 <record id="project.project_project_1" model="project.project">
82 <field name="resource_calendar_id" ref="worktime_9_18" />
83 </record>
84
85 <!-- SLA Definition and Rules -->
86 <record id="sla_resolution" model="project.sla">
87 <field name="name">Standard Resolution Time</field>
88 <field name="control_model">project.issue</field>
89 <field name="control_field_id"
90 ref="project_issue.field_project_issue_date_closed"/>
91 </record>
92 <record id="sla_resolution_rule1" model="project.sla.line">
93 <field name="sla_id" ref="sla_resolution"/>
94 <field name="sequence">10</field>
95 <field name="name">Resolution in two business days</field>
96 <field name="condition">obj.priority &lt;= '2'</field>
97 <field name="limit_qty">16</field>
98 <field name="warn_qty">8</field>
99 </record>
100 <record id="sla_resolution_rule2" model="project.sla.line">
101 <field name="sla_id" ref="sla_resolution"/>
102 <field name="sequence">20</field>
103 <field name="name">Resolution in three business days</field>
104 <field name="condition"></field>
105 <field name="limit_qty">24</field>
106 <field name="warn_qty">16</field>
107 </record>
108
109 <record id="sla_response" model="project.sla">
110 <field name="name">Standard Response Time</field>
111 <field name="control_model">project.issue</field>
112 <field name="control_field_id"
113 ref="project_issue.field_project_issue_date_open"/>
114 </record>
115 <record id="sla_response_rule1" model="project.sla.line">
116 <field name="sla_id" ref="sla_response"/>
117 <field name="sequence">10</field>
118 <field name="name">Response in one business day</field>
119 <field name="condition">obj.priority &lt;= '2'</field>
120 <field name="limit_qty">8</field>
121 <field name="warn_qty">4</field>
122 </record>
123 <record id="sla_response_rule2" model="project.sla.line">
124 <field name="sla_id" ref="sla_response"/>
125 <field name="sequence">20</field>
126 <field name="name">Response in two business days</field>
127 <field name="condition"></field>
128 <field name="limit_qty">16</field>
129 <field name="warn_qty">8</field>
130 </record>
131
132 <!-- Set Contract Resolution SLA Definition -->
133 <record id="project.project_project_1_account_analytic_account" model="account.analytic.account">
134 <field name="sla_ids" eval="[(6, 0, [ref('sla_resolution')])]" />
135 </record>
136
137 </data>
138</openerp>
0139
=== added file 'project_sla/project_sla_view.xml'
--- project_sla/project_sla_view.xml 1970-01-01 00:00:00 +0000
+++ project_sla/project_sla_view.xml 2013-12-26 15:27:30 +0000
@@ -0,0 +1,48 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <record id="view_sla_lines_tree" model="ir.ui.view">
6 <field name="name">view_sla_lines_tree</field>
7 <field name="model">project.sla.line</field>
8 <field name="arch" type="xml">
9
10 <tree string="Definitions">
11 <field name="sequence"/>
12 <field name="name"/>
13 <field name="condition"/>
14 <field name="limit_qty"/>
15 <field name="warn_qty"/>
16 </tree>
17
18 </field>
19 </record>
20
21 <record id="view_sla_form" model="ir.ui.view">
22 <field name="name">view_sla_form</field>
23 <field name="model">project.sla</field>
24 <field name="arch" type="xml">
25
26 <form string="SLA Definition">
27 <field name="name"/>
28 <field name="active"/>
29 <field name="control_model"/>
30 <field name="control_field_id"/>
31 <notebook colspan="4">
32 <page string="Rules" name="rules_page">
33 <field name="sla_line_ids" nolabel="1"/>
34 </page>
35 <page string="Contracts" name="contracts_page">
36 <field name="analytic_ids" nolabel="1" />
37 </page>
38 </notebook>
39 <button name="reapply_slas" colspan="2"
40 string="Reapply SLA on Contracts"
41 type="object" />
42 </form>
43
44 </field>
45 </record>
46
47 </data>
48</openerp>
049
=== added file 'project_sla/project_view.xml'
--- project_sla/project_view.xml 1970-01-01 00:00:00 +0000
+++ project_sla/project_view.xml 2013-12-26 15:27:30 +0000
@@ -0,0 +1,20 @@
1<?xml version="1.0" encoding="utf-8"?>
2<openerp>
3 <data>
4
5 <record id="edit_project_sla" model="ir.ui.view">
6 <field name="name">edit_project_sla</field>
7 <field name="model">project.project</field>
8 <field name="inherit_id" ref="project.edit_project"/>
9 <field name="arch" type="xml">
10
11 <!-- make resource calendar always visible -->
12 <group string="Administration" position="attributes">
13 <attribute name="groups"/>
14 </group>
15
16 </field>
17 </record>
18
19 </data>
20</openerp>
021
=== added directory 'project_sla/security'
=== added file 'project_sla/security/ir.model.access.csv'
--- project_sla/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
+++ project_sla/security/ir.model.access.csv 2013-12-26 15:27:30 +0000
@@ -0,0 +1,8 @@
1id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2access_sla_manager,access_sla_manager,model_project_sla,project.group_project_manager,1,1,1,1
3access_sla_user,access_sla_user,model_project_sla,base.group_user,1,0,0,0
4access_sla_lines_manager,access_sla_lines_manager,model_project_sla_line,project.group_project_manager,1,1,1,1
5access_sla_lines_user,access_sla_lines_user,model_project_sla_line,base.group_user,1,0,0,0
6access_sla_control_manager,access_sla_control_manager,model_project_sla_control,project.group_project_manager,1,1,0,0
7access_sla_control_user,access_sla_control_user,model_project_sla_control,base.group_user,1,0,0,0
8access_sla_controlled_manager,access_sla_controlled_manager,model_project_sla_controlled,project.group_project_manager,1,1,1,1
09
=== added directory 'project_sla/static'
=== added directory 'project_sla/static/src'
=== added directory 'project_sla/static/src/img'
=== added file 'project_sla/static/src/img/icon.png'
1Binary files project_sla/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and project_sla/static/src/img/icon.png 2013-12-26 15:27:30 +0000 differ10Binary files project_sla/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and project_sla/static/src/img/icon.png 2013-12-26 15:27:30 +0000 differ
=== added directory 'project_sla/test'
=== added file 'project_sla/test/project_sla.yml'
--- project_sla/test/project_sla.yml 1970-01-01 00:00:00 +0000
+++ project_sla/test/project_sla.yml 2013-12-26 15:27:30 +0000
@@ -0,0 +1,66 @@
1-
2 Cleanup previous test run
3-
4 !python {model: project.issue}: |
5 res = self.search(cr, uid, [('name', '=', 'My monitor is flickering')])
6 self.unlink(cr, uid, res)
7-
8 Create a new Issue
9-
10 !record {model: project.issue, id: issue1, view: False}:
11 name: "My monitor is flickering"
12 project_id: project.project_project_1
13 priority: "3"
14 user_id: base.user_root
15 partner_id: base.res_partner_2
16 email_from: agr@agrolait.com
17 categ_ids:
18 - project_issue.project_issue_category_01
19-
20 Close the Issue
21-
22 !python {model: project.issue}: |
23 self.case_close(cr, uid, [ref("issue1")])
24-
25 Force the Issue's Create Date and Close Date
26 Created friday before opening hour, closed on next monday near closing hour
27-
28 !python {model: project.issue}: |
29 import time
30 self.write(cr, uid, [ref("issue1"),], {
31 'create_date': time.strftime('2013-11-22 06:15:00'),
32 'date_closed': time.strftime('2013-11-25 16:45:00'),
33 })
34-
35 There should be Service Level info generated on the Issue
36-
37 !assert {model: project.issue, id: issue1, string: Issue should have calculated service levels}:
38 - len(sla_control_ids) == 2
39-
40 Assign an additional "Response SLA" to the Contract
41-
42 !python {model: account.analytic.account}: |
43 self.write(cr, uid, [ref('project.project_project_1_account_analytic_account')],
44 {'sla_ids': [(4, ref('sla_response'))]})
45-
46 Button to Reapply the SLA Definition
47-
48 !python {model: project.sla}: |
49 self._reapply_slas(cr, uid, [ref('sla_resolution')], recalc_closed=True)
50-
51 There should be two Service Level lines generated on the Issue
52-
53 !assert {model: project.issue, id: issue1, string: Issue should have two calculated service levels}:
54 - len(sla_control_ids) == 2
55-
56 The Issue's Resolution SLA should be "3 business days"
57-
58 !python {model: project.issue}: |
59 issue = self.browse(cr, uid, ref('issue1'))
60 for x in issue.sla_control_ids:
61 print x.sla_line_id.name
62 if x.sla_line_id.id == ref("sla_resolution_rule2"):
63 assert x.sla_achieved == 1, "Issue resolution SLA should be achieved"
64 break
65 else:
66 assert False, 'Issue Resolution SLA should be "3 business days"'

Subscribers

People subscribed via source and target branches