Merge lp:~dreis-pt/contract-management/7.0-project_sla-dr into lp:~contract-management-core-editors/contract-management/7.0
- 7.0-project_sla-dr
- Merge into 7.0
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 |
Related bugs: |
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 |
Commit message
Description of the change
New module for SLA control.
Replaces the former MP: https:/
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
Daniel Reis (dreis-pt) wrote : | # |
Good to know that, thanks.
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : | # |
PEP8 issues:
project_
project_
project_
project_
project_
project_
project_
project_
project_
Code issues:
project_
project_
project_
project_
project_
project_
project_
project_
project_
project_
project_
project_
project_
Minor code issues:
project_
project_
project_
project_
project_
project_
project_
project_
project_
Spelling:
project_
project_
project_
project_
project_
- 10. By Daniel Reis
-
Minor fixes
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:/
The E241 were fixed.
> Code issues:
> project_
> 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_
> dt.now(
> project_
> project_
> project_
> project_
Yes; fixed.
> project_
> project_
> project_
> project_
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://
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : | # |
> > project_
> > project_
> > project_
> > project_
>
> 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_
project_
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:/
Essentially if you invoke _(self.
#, 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://
> 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.
Daniel Reis (dreis-pt) wrote : | # |
> project_
> project_
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:/
> /openobject-
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.
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : | # |
LGTM
Preview Diff
1 | === added directory 'project_sla' | |||
2 | === added file 'project_sla/__init__.py' | |||
3 | --- project_sla/__init__.py 1970-01-01 00:00:00 +0000 | |||
4 | +++ project_sla/__init__.py 2013-12-26 15:27:30 +0000 | |||
5 | @@ -0,0 +1,5 @@ | |||
6 | 1 | # -*- coding: utf-8 -*- | ||
7 | 2 | import project_sla | ||
8 | 3 | import analytic_account | ||
9 | 4 | import project_sla_control | ||
10 | 5 | import project_issue | ||
11 | 0 | 6 | ||
12 | === added file 'project_sla/__openerp__.py' | |||
13 | --- project_sla/__openerp__.py 1970-01-01 00:00:00 +0000 | |||
14 | +++ project_sla/__openerp__.py 2013-12-26 15:27:30 +0000 | |||
15 | @@ -0,0 +1,132 @@ | |||
16 | 1 | # -*- coding: utf-8 -*- | ||
17 | 2 | ############################################################################## | ||
18 | 3 | # | ||
19 | 4 | # Copyright (C) 2013 Daniel Reis | ||
20 | 5 | # | ||
21 | 6 | # This program is free software: you can redistribute it and/or modify | ||
22 | 7 | # it under the terms of the GNU Affero General Public License as | ||
23 | 8 | # published by the Free Software Foundation, either version 3 of the | ||
24 | 9 | # License, or (at your option) any later version. | ||
25 | 10 | # | ||
26 | 11 | # This program is distributed in the hope that it will be useful, | ||
27 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
28 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
29 | 14 | # GNU Affero General Public License for more details. | ||
30 | 15 | # | ||
31 | 16 | # You should have received a copy of the GNU Affero General Public License | ||
32 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
33 | 18 | # | ||
34 | 19 | ############################################################################## | ||
35 | 20 | { | ||
36 | 21 | 'name': 'Service Level Agreements', | ||
37 | 22 | 'summary': 'Define SLAs for your Contracts', | ||
38 | 23 | 'version': '1.0', | ||
39 | 24 | "category": "Project Management", | ||
40 | 25 | 'description': """\ | ||
41 | 26 | Contract SLAs | ||
42 | 27 | =============== | ||
43 | 28 | |||
44 | 29 | SLAs are assigned to Contracts, on the Analytic Account form, SLA Definition | ||
45 | 30 | separator. This is also where new SLA Definitions are created. | ||
46 | 31 | |||
47 | 32 | One Contract can have several SLA Definitions attached, allowing for | ||
48 | 33 | "composite SLAs". For example, a contract could have a Response Time SLA (time | ||
49 | 34 | to start resolution) and a Resolution Time SLA (time to close request). | ||
50 | 35 | |||
51 | 36 | |||
52 | 37 | SLA Controlled Documents | ||
53 | 38 | ======================== | ||
54 | 39 | |||
55 | 40 | Only Project Issue documents are made SLA controllable. | ||
56 | 41 | However, a framework is made available to easily build extensions to make | ||
57 | 42 | other documents models SLA controlled. | ||
58 | 43 | |||
59 | 44 | SLA controlled documents have attached information on the list of SLA rules | ||
60 | 45 | they should meet (more than one in the case for composite SLAs) and a summary | ||
61 | 46 | SLA status: | ||
62 | 47 | |||
63 | 48 | * "watching" the service level (it has SLA requirements to meet) | ||
64 | 49 | * under "warning" (limit dates are close, special attention is needed) | ||
65 | 50 | * "failed" (one on the SLA limits has not been met) | ||
66 | 51 | * "achieved" (all SLA limits have been met) | ||
67 | 52 | |||
68 | 53 | Transient states, such as "watching" and "warning", are regularly updated by | ||
69 | 54 | a hourly scheduled job, that reevaluates the warning and limit dates against | ||
70 | 55 | the current time and changes the state when find dates that have been exceeded. | ||
71 | 56 | |||
72 | 57 | To decide what SLA Definitions apply for a specific document, first a lookup | ||
73 | 58 | is made for a ``analytic_account_id`` field. If not found, then it will | ||
74 | 59 | look up for the ``project_id`` and it's corresponding ``analytic_account_id``. | ||
75 | 60 | |||
76 | 61 | Specifically, the Service Desk module introduces a Analytic Account field for | ||
77 | 62 | Project Issues. This makes it possible for a Service Team (a "Project") to | ||
78 | 63 | have a generic SLA, but at the same time allow for some Contracts to have | ||
79 | 64 | specific SLAs (such as the case for "premium" service conditions). | ||
80 | 65 | |||
81 | 66 | |||
82 | 67 | SLA Definitions and Rules | ||
83 | 68 | ========================= | ||
84 | 69 | |||
85 | 70 | New SLA Definitions are created from the Analytic Account form, SLA Definition | ||
86 | 71 | field. | ||
87 | 72 | |||
88 | 73 | Each definition can have one or more Rules. | ||
89 | 74 | The particular rule to use is decided by conditions, so that you can set | ||
90 | 75 | different service levels based on request attributes, such as Priority or | ||
91 | 76 | Category. | ||
92 | 77 | Each rule condition is evaluated in "sequence" order, and the first onea to met | ||
93 | 78 | is the one to be used. | ||
94 | 79 | In the simplest case, a single rule with no condition is just what is needed. | ||
95 | 80 | |||
96 | 81 | Each rule sets a number of hours until the "limit date", and the number of | ||
97 | 82 | hours until a "warning date". The former will be used to decide if the SLA | ||
98 | 83 | was achieved, and the later can be used for automatic alarms or escalation | ||
99 | 84 | procedures. | ||
100 | 85 | |||
101 | 86 | Time will be counted from creation date, until the "Control Date" specified for | ||
102 | 87 | the SLA Definition. That would usually be the "Close" (time until resolution) | ||
103 | 88 | or the "Open" (time until response) dates. | ||
104 | 89 | |||
105 | 90 | The working calendar set in the related Project definitions will be used (see | ||
106 | 91 | the "Other Info" tab). If none is defined, a builtin "all days, 8-12 13-17" | ||
107 | 92 | default calendar is used. | ||
108 | 93 | |||
109 | 94 | A timezone and leave calendars will also used, based on either the assigned | ||
110 | 95 | user (document's `user_id`) or on the current user. | ||
111 | 96 | |||
112 | 97 | |||
113 | 98 | Setup checklist | ||
114 | 99 | =============== | ||
115 | 100 | |||
116 | 101 | The basic steps to configure SLAs for a Project are: | ||
117 | 102 | |||
118 | 103 | * Set Project's Working Calendar, at Project definitions, "Other Info" tab | ||
119 | 104 | * Go to the Project's Analytic Account form; create and set SLA Definitions | ||
120 | 105 | * Use the "Reapply SLAs" button on the Analytic Account form | ||
121 | 106 | * See Project Issue's calculated SLAs in the new "Service Levels" tab | ||
122 | 107 | |||
123 | 108 | |||
124 | 109 | Credits and Contributors | ||
125 | 110 | ======================== | ||
126 | 111 | |||
127 | 112 | * Daniel Reis (https://launchpad.net/~dreis-pt) | ||
128 | 113 | * David Vignoni, author of the icon from the KDE 3.x Nuvola icon theme | ||
129 | 114 | """, | ||
130 | 115 | 'author': 'Daniel Reis', | ||
131 | 116 | 'website': '', | ||
132 | 117 | 'depends': [ | ||
133 | 118 | 'project_issue', | ||
134 | 119 | ], | ||
135 | 120 | 'data': [ | ||
136 | 121 | 'project_sla_view.xml', | ||
137 | 122 | 'project_sla_control_view.xml', | ||
138 | 123 | 'project_sla_control_data.xml', | ||
139 | 124 | 'analytic_account_view.xml', | ||
140 | 125 | 'project_view.xml', | ||
141 | 126 | 'project_issue_view.xml', | ||
142 | 127 | 'security/ir.model.access.csv', | ||
143 | 128 | ], | ||
144 | 129 | 'demo': ['project_sla_demo.xml'], | ||
145 | 130 | 'test': ['test/project_sla.yml'], | ||
146 | 131 | 'installable': True, | ||
147 | 132 | } | ||
148 | 0 | 133 | ||
149 | === added file 'project_sla/analytic_account.py' | |||
150 | --- project_sla/analytic_account.py 1970-01-01 00:00:00 +0000 | |||
151 | +++ project_sla/analytic_account.py 2013-12-26 15:27:30 +0000 | |||
152 | @@ -0,0 +1,69 @@ | |||
153 | 1 | # -*- coding: utf-8 -*- | ||
154 | 2 | ############################################################################## | ||
155 | 3 | # | ||
156 | 4 | # Copyright (C) 2013 Daniel Reis | ||
157 | 5 | # | ||
158 | 6 | # This program is free software: you can redistribute it and/or modify | ||
159 | 7 | # it under the terms of the GNU Affero General Public License as | ||
160 | 8 | # published by the Free Software Foundation, either version 3 of the | ||
161 | 9 | # License, or (at your option) any later version. | ||
162 | 10 | # | ||
163 | 11 | # This program is distributed in the hope that it will be useful, | ||
164 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
165 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
166 | 14 | # GNU Affero General Public License for more details. | ||
167 | 15 | # | ||
168 | 16 | # You should have received a copy of the GNU Affero General Public License | ||
169 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
170 | 18 | # | ||
171 | 19 | ############################################################################## | ||
172 | 20 | |||
173 | 21 | from openerp.osv import fields, orm | ||
174 | 22 | |||
175 | 23 | |||
176 | 24 | class AnalyticAccount(orm.Model): | ||
177 | 25 | """ Add SLA to Analytic Accounts """ | ||
178 | 26 | _inherit = 'account.analytic.account' | ||
179 | 27 | _columns = { | ||
180 | 28 | 'sla_ids': fields.many2many( | ||
181 | 29 | 'project.sla', string='Service Level Agreement'), | ||
182 | 30 | } | ||
183 | 31 | |||
184 | 32 | def _reapply_sla(self, cr, uid, ids, recalc_closed=False, context=None): | ||
185 | 33 | """ | ||
186 | 34 | Force SLA recalculation on open documents that already are subject to | ||
187 | 35 | this SLA Definition. | ||
188 | 36 | To use after changing a Contract SLA or it's Definitions. | ||
189 | 37 | The ``recalc_closed`` flag allows to also recompute closed documents. | ||
190 | 38 | """ | ||
191 | 39 | ctrl_obj = self.pool.get('project.sla.control') | ||
192 | 40 | proj_obj = self.pool.get('project.project') | ||
193 | 41 | exclude_states = ['cancelled'] + (not recalc_closed and ['done'] or []) | ||
194 | 42 | for contract in self.browse(cr, uid, ids, context=context): | ||
195 | 43 | # for each contract, and for each model under SLA control ... | ||
196 | 44 | for m_name in set([sla.control_model for sla in contract.sla_ids]): | ||
197 | 45 | model = self.pool.get(m_name) | ||
198 | 46 | doc_ids = [] | ||
199 | 47 | if 'analytic_account_id' in model._columns: | ||
200 | 48 | doc_ids += model.search( | ||
201 | 49 | cr, uid, | ||
202 | 50 | [('analytic_account_id', '=', contract.id), | ||
203 | 51 | ('state', 'not in', exclude_states)], | ||
204 | 52 | context=context) | ||
205 | 53 | if 'project_id' in model._columns: | ||
206 | 54 | proj_ids = proj_obj.search( | ||
207 | 55 | cr, uid, [('analytic_account_id', '=', contract.id)], | ||
208 | 56 | context=context) | ||
209 | 57 | doc_ids += model.search( | ||
210 | 58 | cr, uid, | ||
211 | 59 | [('project_id', 'in', proj_ids), | ||
212 | 60 | ('state', 'not in', exclude_states)], | ||
213 | 61 | context=context) | ||
214 | 62 | if doc_ids: | ||
215 | 63 | docs = model.browse(cr, uid, doc_ids, context=context) | ||
216 | 64 | ctrl_obj.store_sla_control(cr, uid, docs, context=context) | ||
217 | 65 | return True | ||
218 | 66 | |||
219 | 67 | def reapply_sla(self, cr, uid, ids, context=None): | ||
220 | 68 | """ Reapply SLAs button action """ | ||
221 | 69 | return self._reapply_sla(cr, uid, ids, context=context) | ||
222 | 0 | 70 | ||
223 | === added file 'project_sla/analytic_account_view.xml' | |||
224 | --- project_sla/analytic_account_view.xml 1970-01-01 00:00:00 +0000 | |||
225 | +++ project_sla/analytic_account_view.xml 2013-12-26 15:27:30 +0000 | |||
226 | @@ -0,0 +1,24 @@ | |||
227 | 1 | <?xml version="1.0" encoding="utf-8"?> | ||
228 | 2 | <openerp> | ||
229 | 3 | <data> | ||
230 | 4 | |||
231 | 5 | <record id="view_account_analytic_account_form_sla" model="ir.ui.view"> | ||
232 | 6 | <field name="name">view_account_analytic_account_form_sla</field> | ||
233 | 7 | <field name="model">account.analytic.account</field> | ||
234 | 8 | <field name="inherit_id" ref="analytic.view_account_analytic_account_form"/> | ||
235 | 9 | <field name="arch" type="xml"> | ||
236 | 10 | |||
237 | 11 | <page name="contract_page" position="after"> | ||
238 | 12 | <page name="sla_page" string="Service Level Agreement"> | ||
239 | 13 | <field name="sla_ids" nolabel="1"/> | ||
240 | 14 | <button name="reapply_sla" string="Reapply" type="object" | ||
241 | 15 | help="Reapply the SLAs to all Contract's documents." | ||
242 | 16 | groups="project.group_project_manager" /> | ||
243 | 17 | </page> | ||
244 | 18 | </page> | ||
245 | 19 | |||
246 | 20 | </field> | ||
247 | 21 | </record> | ||
248 | 22 | |||
249 | 23 | </data> | ||
250 | 24 | </openerp> | ||
251 | 0 | 25 | ||
252 | === added directory 'project_sla/i18n' | |||
253 | === added file 'project_sla/i18n/project_sla.pot' | |||
254 | --- project_sla/i18n/project_sla.pot 1970-01-01 00:00:00 +0000 | |||
255 | +++ project_sla/i18n/project_sla.pot 2013-12-26 15:27:30 +0000 | |||
256 | @@ -0,0 +1,296 @@ | |||
257 | 1 | # Translation of OpenERP Server. | ||
258 | 2 | # This file contains the translation of the following modules: | ||
259 | 3 | # * project_sla | ||
260 | 4 | # | ||
261 | 5 | msgid "" | ||
262 | 6 | msgstr "" | ||
263 | 7 | "Project-Id-Version: OpenERP Server 7.0\n" | ||
264 | 8 | "Report-Msgid-Bugs-To: \n" | ||
265 | 9 | "POT-Creation-Date: 2013-12-19 10:28+0000\n" | ||
266 | 10 | "PO-Revision-Date: 2013-12-19 10:28+0000\n" | ||
267 | 11 | "Last-Translator: <>\n" | ||
268 | 12 | "Language-Team: \n" | ||
269 | 13 | "MIME-Version: 1.0\n" | ||
270 | 14 | "Content-Type: text/plain; charset=UTF-8\n" | ||
271 | 15 | "Content-Transfer-Encoding: \n" | ||
272 | 16 | "Plural-Forms: \n" | ||
273 | 17 | |||
274 | 18 | #. module: project_sla | ||
275 | 19 | #: model:project.sla,name:project_sla.sla_response | ||
276 | 20 | msgid "Standard Response Time" | ||
277 | 21 | msgstr "" | ||
278 | 22 | |||
279 | 23 | #. module: project_sla | ||
280 | 24 | #: help:project.sla,control_field_id:0 | ||
281 | 25 | msgid "Date field used to check if the SLA was achieved." | ||
282 | 26 | msgstr "" | ||
283 | 27 | |||
284 | 28 | #. module: project_sla | ||
285 | 29 | #: model:ir.model,name:project_sla.model_project_issue | ||
286 | 30 | msgid "Project Issue" | ||
287 | 31 | msgstr "" | ||
288 | 32 | |||
289 | 33 | #. module: project_sla | ||
290 | 34 | #: model:ir.model,name:project_sla.model_project_sla_control | ||
291 | 35 | msgid "SLA Control Registry" | ||
292 | 36 | msgstr "" | ||
293 | 37 | |||
294 | 38 | #. module: project_sla | ||
295 | 39 | #: view:project.sla:0 | ||
296 | 40 | msgid "Reapply SLA on Contracts" | ||
297 | 41 | msgstr "" | ||
298 | 42 | |||
299 | 43 | #. module: project_sla | ||
300 | 44 | #: view:project.project:0 | ||
301 | 45 | msgid "Administration" | ||
302 | 46 | msgstr "" | ||
303 | 47 | |||
304 | 48 | #. module: project_sla | ||
305 | 49 | #: view:project.issue:0 | ||
306 | 50 | msgid "Priority" | ||
307 | 51 | msgstr "" | ||
308 | 52 | |||
309 | 53 | #. module: project_sla | ||
310 | 54 | #: selection:project.issue,sla_state:0 | ||
311 | 55 | #: selection:project.sla.control,sla_state:0 | ||
312 | 56 | #: selection:project.sla.controlled,sla_state:0 | ||
313 | 57 | msgid "Failed" | ||
314 | 58 | msgstr "" | ||
315 | 59 | |||
316 | 60 | #. module: project_sla | ||
317 | 61 | #: field:project.sla.control,sla_warn_date:0 | ||
318 | 62 | msgid "Warning Date" | ||
319 | 63 | msgstr "" | ||
320 | 64 | |||
321 | 65 | #. module: project_sla | ||
322 | 66 | #: field:project.sla.line,warn_qty:0 | ||
323 | 67 | msgid "Hours to Warn" | ||
324 | 68 | msgstr "" | ||
325 | 69 | |||
326 | 70 | #. module: project_sla | ||
327 | 71 | #: view:project.sla.control:0 | ||
328 | 72 | msgid "Service Level" | ||
329 | 73 | msgstr "" | ||
330 | 74 | |||
331 | 75 | #. module: project_sla | ||
332 | 76 | #: selection:project.issue,sla_state:0 | ||
333 | 77 | #: selection:project.sla.control,sla_state:0 | ||
334 | 78 | #: selection:project.sla.controlled,sla_state:0 | ||
335 | 79 | msgid "Watching" | ||
336 | 80 | msgstr "" | ||
337 | 81 | |||
338 | 82 | #. module: project_sla | ||
339 | 83 | #: view:project.sla:0 | ||
340 | 84 | #: field:project.sla,analytic_ids:0 | ||
341 | 85 | msgid "Contracts" | ||
342 | 86 | msgstr "" | ||
343 | 87 | |||
344 | 88 | #. module: project_sla | ||
345 | 89 | #: field:project.sla,name:0 | ||
346 | 90 | #: field:project.sla.line,name:0 | ||
347 | 91 | msgid "Title" | ||
348 | 92 | msgstr "" | ||
349 | 93 | |||
350 | 94 | #. module: project_sla | ||
351 | 95 | #: field:project.sla,active:0 | ||
352 | 96 | msgid "Active" | ||
353 | 97 | msgstr "" | ||
354 | 98 | |||
355 | 99 | #. module: project_sla | ||
356 | 100 | #: field:project.issue,sla_control_ids:0 | ||
357 | 101 | #: field:project.sla.controlled,sla_control_ids:0 | ||
358 | 102 | msgid "SLA Control" | ||
359 | 103 | msgstr "" | ||
360 | 104 | |||
361 | 105 | #. module: project_sla | ||
362 | 106 | #: field:project.sla.control,sla_achieved:0 | ||
363 | 107 | msgid "Achieved?" | ||
364 | 108 | msgstr "" | ||
365 | 109 | |||
366 | 110 | #. module: project_sla | ||
367 | 111 | #: view:project.issue:0 | ||
368 | 112 | #: field:project.issue,sla_state:0 | ||
369 | 113 | #: field:project.sla.control,sla_state:0 | ||
370 | 114 | #: field:project.sla.controlled,sla_state:0 | ||
371 | 115 | msgid "SLA Status" | ||
372 | 116 | msgstr "" | ||
373 | 117 | |||
374 | 118 | #. module: project_sla | ||
375 | 119 | #: field:project.sla.line,condition:0 | ||
376 | 120 | msgid "Condition" | ||
377 | 121 | msgstr "" | ||
378 | 122 | |||
379 | 123 | #. module: project_sla | ||
380 | 124 | #: field:project.sla,control_model:0 | ||
381 | 125 | msgid "For documents" | ||
382 | 126 | msgstr "" | ||
383 | 127 | |||
384 | 128 | #. module: project_sla | ||
385 | 129 | #: view:project.sla:0 | ||
386 | 130 | #: field:project.sla.line,sla_id:0 | ||
387 | 131 | msgid "SLA Definition" | ||
388 | 132 | msgstr "" | ||
389 | 133 | |||
390 | 134 | #. module: project_sla | ||
391 | 135 | #: model:project.sla.line,name:project_sla.sla_response_rule2 | ||
392 | 136 | msgid "Response in two business days" | ||
393 | 137 | msgstr "" | ||
394 | 138 | |||
395 | 139 | #. module: project_sla | ||
396 | 140 | #: selection:project.issue,sla_state:0 | ||
397 | 141 | #: selection:project.sla.control,sla_state:0 | ||
398 | 142 | #: selection:project.sla.controlled,sla_state:0 | ||
399 | 143 | msgid "Will Fail" | ||
400 | 144 | msgstr "" | ||
401 | 145 | |||
402 | 146 | #. module: project_sla | ||
403 | 147 | #: field:project.sla.control,sla_line_id:0 | ||
404 | 148 | msgid "Service Agreement" | ||
405 | 149 | msgstr "" | ||
406 | 150 | |||
407 | 151 | #. module: project_sla | ||
408 | 152 | #: field:project.sla.control,doc_id:0 | ||
409 | 153 | msgid "Document ID" | ||
410 | 154 | msgstr "" | ||
411 | 155 | |||
412 | 156 | #. module: project_sla | ||
413 | 157 | #: field:project.sla.control,locked:0 | ||
414 | 158 | msgid "Recalculation disabled" | ||
415 | 159 | msgstr "" | ||
416 | 160 | |||
417 | 161 | #. module: project_sla | ||
418 | 162 | #: field:project.sla.control,sla_limit_date:0 | ||
419 | 163 | msgid "Limit Date" | ||
420 | 164 | msgstr "" | ||
421 | 165 | |||
422 | 166 | #. module: project_sla | ||
423 | 167 | #: help:project.sla.line,condition:0 | ||
424 | 168 | 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'" | ||
425 | 169 | msgstr "" | ||
426 | 170 | |||
427 | 171 | #. module: project_sla | ||
428 | 172 | #: model:project.sla.line,name:project_sla.sla_resolution_rule1 | ||
429 | 173 | msgid "Resolution in two business days" | ||
430 | 174 | msgstr "" | ||
431 | 175 | |||
432 | 176 | #. module: project_sla | ||
433 | 177 | #: view:account.analytic.account:0 | ||
434 | 178 | msgid "Reapply" | ||
435 | 179 | msgstr "" | ||
436 | 180 | |||
437 | 181 | #. module: project_sla | ||
438 | 182 | #: field:project.sla.line,limit_qty:0 | ||
439 | 183 | msgid "Hours to Limit" | ||
440 | 184 | msgstr "" | ||
441 | 185 | |||
442 | 186 | #. module: project_sla | ||
443 | 187 | #: field:project.sla,sla_line_ids:0 | ||
444 | 188 | #: view:project.sla.line:0 | ||
445 | 189 | msgid "Definitions" | ||
446 | 190 | msgstr "" | ||
447 | 191 | |||
448 | 192 | #. module: project_sla | ||
449 | 193 | #: model:project.sla.line,name:project_sla.sla_resolution_rule2 | ||
450 | 194 | msgid "Resolution in three business days" | ||
451 | 195 | msgstr "" | ||
452 | 196 | |||
453 | 197 | #. module: project_sla | ||
454 | 198 | #: field:project.sla.control,sla_close_date:0 | ||
455 | 199 | msgid "Close Date" | ||
456 | 200 | msgstr "" | ||
457 | 201 | |||
458 | 202 | #. module: project_sla | ||
459 | 203 | #: model:ir.model,name:project_sla.model_project_sla | ||
460 | 204 | msgid "project.sla" | ||
461 | 205 | msgstr "" | ||
462 | 206 | |||
463 | 207 | #. module: project_sla | ||
464 | 208 | #: field:project.sla,control_field_id:0 | ||
465 | 209 | msgid "Control Date" | ||
466 | 210 | msgstr "" | ||
467 | 211 | |||
468 | 212 | #. module: project_sla | ||
469 | 213 | #: model:project.sla.line,name:project_sla.sla_response_rule1 | ||
470 | 214 | msgid "Response in one business day" | ||
471 | 215 | msgstr "" | ||
472 | 216 | |||
473 | 217 | #. module: project_sla | ||
474 | 218 | #: selection:project.issue,sla_state:0 | ||
475 | 219 | #: selection:project.sla.control,sla_state:0 | ||
476 | 220 | #: selection:project.sla.controlled,sla_state:0 | ||
477 | 221 | msgid "Achieved" | ||
478 | 222 | msgstr "" | ||
479 | 223 | |||
480 | 224 | #. module: project_sla | ||
481 | 225 | #: view:account.analytic.account:0 | ||
482 | 226 | #: field:account.analytic.account,sla_ids:0 | ||
483 | 227 | msgid "Service Level Agreement" | ||
484 | 228 | msgstr "" | ||
485 | 229 | |||
486 | 230 | #. module: project_sla | ||
487 | 231 | #: model:project.sla,name:project_sla.sla_resolution | ||
488 | 232 | msgid "Standard Resolution Time" | ||
489 | 233 | msgstr "" | ||
490 | 234 | |||
491 | 235 | #. module: project_sla | ||
492 | 236 | #: field:project.sla.line,sequence:0 | ||
493 | 237 | msgid "Sequence" | ||
494 | 238 | msgstr "" | ||
495 | 239 | |||
496 | 240 | #. module: project_sla | ||
497 | 241 | #: view:project.issue:0 | ||
498 | 242 | msgid "Service Levels" | ||
499 | 243 | msgstr "" | ||
500 | 244 | |||
501 | 245 | #. module: project_sla | ||
502 | 246 | #: model:ir.model,name:project_sla.model_account_analytic_account | ||
503 | 247 | msgid "Analytic Account" | ||
504 | 248 | msgstr "" | ||
505 | 249 | |||
506 | 250 | #. module: project_sla | ||
507 | 251 | #: view:project.sla:0 | ||
508 | 252 | msgid "Rules" | ||
509 | 253 | msgstr "" | ||
510 | 254 | |||
511 | 255 | #. module: project_sla | ||
512 | 256 | #: help:project.sla.control,locked:0 | ||
513 | 257 | msgid "Safeguard manual changes from future automatic recomputations." | ||
514 | 258 | msgstr "" | ||
515 | 259 | |||
516 | 260 | #. module: project_sla | ||
517 | 261 | #: selection:project.issue,sla_state:0 | ||
518 | 262 | #: selection:project.sla.control,sla_state:0 | ||
519 | 263 | #: selection:project.sla.controlled,sla_state:0 | ||
520 | 264 | msgid "Warning" | ||
521 | 265 | msgstr "" | ||
522 | 266 | |||
523 | 267 | #. module: project_sla | ||
524 | 268 | #: view:project.issue:0 | ||
525 | 269 | msgid "Extra Info" | ||
526 | 270 | msgstr "" | ||
527 | 271 | |||
528 | 272 | #. module: project_sla | ||
529 | 273 | #: field:project.sla.control,doc_model:0 | ||
530 | 274 | msgid "Document Model" | ||
531 | 275 | msgstr "" | ||
532 | 276 | |||
533 | 277 | #. module: project_sla | ||
534 | 278 | #: model:ir.model,name:project_sla.model_project_sla_line | ||
535 | 279 | msgid "project.sla.line" | ||
536 | 280 | msgstr "" | ||
537 | 281 | |||
538 | 282 | #. module: project_sla | ||
539 | 283 | #: view:account.analytic.account:0 | ||
540 | 284 | msgid "Reapply the SLAs to all Contract's documents." | ||
541 | 285 | msgstr "" | ||
542 | 286 | |||
543 | 287 | #. module: project_sla | ||
544 | 288 | #: field:project.sla.control,sla_start_date:0 | ||
545 | 289 | msgid "Start Date" | ||
546 | 290 | msgstr "" | ||
547 | 291 | |||
548 | 292 | #. module: project_sla | ||
549 | 293 | #: model:ir.model,name:project_sla.model_project_sla_controlled | ||
550 | 294 | msgid "SLA Controlled Document" | ||
551 | 295 | msgstr "" | ||
552 | 296 | |||
553 | 0 | 297 | ||
554 | === added directory 'project_sla/images' | |||
555 | === added file 'project_sla/images/10_sla_contract.png' | |||
556 | 1 | Binary 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 | 298 | Binary 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 |
557 | === added file 'project_sla/images/20_sla_definition.png' | |||
558 | 2 | Binary 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 | 299 | Binary 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 |
559 | === added file 'project_sla/images/30_sla_controlled.png' | |||
560 | 3 | Binary 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 | 300 | Binary 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 |
561 | === added file 'project_sla/m2m.py' | |||
562 | --- project_sla/m2m.py 1970-01-01 00:00:00 +0000 | |||
563 | +++ project_sla/m2m.py 2013-12-26 15:27:30 +0000 | |||
564 | @@ -0,0 +1,75 @@ | |||
565 | 1 | """ | ||
566 | 2 | Wrapper for OpenERP's cryptic write conventions for x2many fields. | ||
567 | 3 | |||
568 | 4 | Example usage: | ||
569 | 5 | |||
570 | 6 | import m2m | ||
571 | 7 | browse_rec.write({'many_ids: m2m.clear()) | ||
572 | 8 | browse_rec.write({'many_ids: m2m.link(99)) | ||
573 | 9 | browse_rec.write({'many_ids: m2m.add({'name': 'Monty'})) | ||
574 | 10 | browse_rec.write({'many_ids: m2m.replace([98, 99])) | ||
575 | 11 | |||
576 | 12 | Since returned values are lists, the can be joined using the plus operator: | ||
577 | 13 | |||
578 | 14 | browse_rec.write({'many_ids: m2m.clear() + m2m.link(99)) | ||
579 | 15 | |||
580 | 16 | (Source: https://github.com/dreispt/openerp-write2many) | ||
581 | 17 | """ | ||
582 | 18 | |||
583 | 19 | |||
584 | 20 | def create(values): | ||
585 | 21 | """ Create a referenced record """ | ||
586 | 22 | assert isinstance(values, dict) | ||
587 | 23 | return [(0, 0, values)] | ||
588 | 24 | |||
589 | 25 | |||
590 | 26 | def add(values): | ||
591 | 27 | """ Intuitive alias for create() """ | ||
592 | 28 | return create(values) | ||
593 | 29 | |||
594 | 30 | |||
595 | 31 | def write(id, values): | ||
596 | 32 | """ Write on referenced record """ | ||
597 | 33 | assert isinstance(id, int) | ||
598 | 34 | assert isinstance(values, dict) | ||
599 | 35 | return [(1, id, values)] | ||
600 | 36 | |||
601 | 37 | |||
602 | 38 | def remove(id): | ||
603 | 39 | """ Unlink and delete referenced record """ | ||
604 | 40 | assert isinstance(id, int) | ||
605 | 41 | return [(2, id)] | ||
606 | 42 | |||
607 | 43 | |||
608 | 44 | def unlink(id): | ||
609 | 45 | """ Unlink but do not delete the referenced record """ | ||
610 | 46 | assert isinstance(id, int) | ||
611 | 47 | return [(3, id)] | ||
612 | 48 | |||
613 | 49 | |||
614 | 50 | def link(id): | ||
615 | 51 | """ Link but do not delete the referenced record """ | ||
616 | 52 | assert isinstance(id, int) | ||
617 | 53 | return [(4, id)] | ||
618 | 54 | |||
619 | 55 | |||
620 | 56 | def clear(): | ||
621 | 57 | """ Unlink all referenced records (doesn't delete them) """ | ||
622 | 58 | return [(5, 0)] | ||
623 | 59 | |||
624 | 60 | |||
625 | 61 | def replace(ids): | ||
626 | 62 | """ Unlink all current records and replace them with a new list """ | ||
627 | 63 | assert isinstance(ids, list) | ||
628 | 64 | return [(6, 0, ids)] | ||
629 | 65 | |||
630 | 66 | |||
631 | 67 | if __name__ == "__main__": | ||
632 | 68 | # Tests: | ||
633 | 69 | assert create({'name': 'Monty'}) == [(0, 0, {'name': 'Monty'})] | ||
634 | 70 | assert write(99, {'name': 'Monty'}) == [(1, 99, {'name': 'Monty'})] | ||
635 | 71 | assert remove(99) == [(2, 99)] | ||
636 | 72 | assert unlink(99) == [(3, 99)] | ||
637 | 73 | assert clear() == [(5, 0)] | ||
638 | 74 | assert replace([97, 98, 99]) == [(6, 0, [97, 98, 99])] | ||
639 | 75 | print("Done!") | ||
640 | 0 | 76 | ||
641 | === added file 'project_sla/project_issue.py' | |||
642 | --- project_sla/project_issue.py 1970-01-01 00:00:00 +0000 | |||
643 | +++ project_sla/project_issue.py 2013-12-26 15:27:30 +0000 | |||
644 | @@ -0,0 +1,29 @@ | |||
645 | 1 | # -*- coding: utf-8 -*- | ||
646 | 2 | ############################################################################## | ||
647 | 3 | # | ||
648 | 4 | # Copyright (C) 2013 Daniel Reis | ||
649 | 5 | # | ||
650 | 6 | # This program is free software: you can redistribute it and/or modify | ||
651 | 7 | # it under the terms of the GNU Affero General Public License as | ||
652 | 8 | # published by the Free Software Foundation, either version 3 of the | ||
653 | 9 | # License, or (at your option) any later version. | ||
654 | 10 | # | ||
655 | 11 | # This program is distributed in the hope that it will be useful, | ||
656 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
657 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
658 | 14 | # GNU Affero General Public License for more details. | ||
659 | 15 | # | ||
660 | 16 | # You should have received a copy of the GNU Affero General Public License | ||
661 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
662 | 18 | # | ||
663 | 19 | ############################################################################## | ||
664 | 20 | |||
665 | 21 | from openerp.osv import orm | ||
666 | 22 | |||
667 | 23 | |||
668 | 24 | class ProjectIssue(orm.Model): | ||
669 | 25 | """ | ||
670 | 26 | Extend Project Issues to be SLA Controlled | ||
671 | 27 | """ | ||
672 | 28 | _name = 'project.issue' | ||
673 | 29 | _inherit = ['project.issue', 'project.sla.controlled'] | ||
674 | 0 | 30 | ||
675 | === added file 'project_sla/project_issue_view.xml' | |||
676 | --- project_sla/project_issue_view.xml 1970-01-01 00:00:00 +0000 | |||
677 | +++ project_sla/project_issue_view.xml 2013-12-26 15:27:30 +0000 | |||
678 | @@ -0,0 +1,60 @@ | |||
679 | 1 | <?xml version="1.0" encoding="utf-8"?> | ||
680 | 2 | <openerp> | ||
681 | 3 | <data> | ||
682 | 4 | |||
683 | 5 | <!-- Project Issue Form --> | ||
684 | 6 | <record id="project_issue_form_view_sla" model="ir.ui.view"> | ||
685 | 7 | <field name="name">project_issue_form_view_sla</field> | ||
686 | 8 | <field name="model">project.issue</field> | ||
687 | 9 | <field name="inherit_id" ref="project_issue.project_issue_form_view"/> | ||
688 | 10 | <field name="arch" type="xml"> | ||
689 | 11 | |||
690 | 12 | <page string="Extra Info" position="after"> | ||
691 | 13 | <page name="sla_page" string="Service Levels" | ||
692 | 14 | attrs="{'invisible': [('sla_state', '=', False)]}"> | ||
693 | 15 | <group> | ||
694 | 16 | <group> | ||
695 | 17 | <field name="sla_state" /> | ||
696 | 18 | </group> | ||
697 | 19 | <group> | ||
698 | 20 | <field name="write_date" /> | ||
699 | 21 | </group> | ||
700 | 22 | </group> | ||
701 | 23 | <field name="sla_control_ids"/> | ||
702 | 24 | </page> | ||
703 | 25 | </page> | ||
704 | 26 | |||
705 | 27 | </field> | ||
706 | 28 | </record> | ||
707 | 29 | |||
708 | 30 | <!-- Project Issue List --> | ||
709 | 31 | <record model="ir.ui.view" id="project_issue_tree_view_sla"> | ||
710 | 32 | <field name="name">project_issue_tree_view_sla</field> | ||
711 | 33 | <field name="model">project.issue</field> | ||
712 | 34 | <field name="inherit_id" ref="project_issue.project_issue_tree_view"/> | ||
713 | 35 | <field name="arch" type="xml"> | ||
714 | 36 | |||
715 | 37 | <field name="project_id" position="after"> | ||
716 | 38 | <field name="sla_state"/> | ||
717 | 39 | </field> | ||
718 | 40 | |||
719 | 41 | </field> | ||
720 | 42 | </record> | ||
721 | 43 | |||
722 | 44 | |||
723 | 45 | <!-- Project Issue Filter --> | ||
724 | 46 | <record id="view_project_issue_filter_sdesk" model="ir.ui.view"> | ||
725 | 47 | <field name="name">view_project_issue_filter_sdesk</field> | ||
726 | 48 | <field name="model">project.issue</field> | ||
727 | 49 | <field name="inherit_id" ref="project_issue.view_project_issue_filter"/> | ||
728 | 50 | <field name="arch" type="xml"> | ||
729 | 51 | |||
730 | 52 | <filter string="Priority" position="after"> | ||
731 | 53 | <filter string="SLA Status" context="{'group_by':'sla_state'}" /> | ||
732 | 54 | </filter> | ||
733 | 55 | |||
734 | 56 | </field> | ||
735 | 57 | </record> | ||
736 | 58 | |||
737 | 59 | </data> | ||
738 | 60 | </openerp> | ||
739 | 0 | 61 | ||
740 | === added file 'project_sla/project_sla.py' | |||
741 | --- project_sla/project_sla.py 1970-01-01 00:00:00 +0000 | |||
742 | +++ project_sla/project_sla.py 2013-12-26 15:27:30 +0000 | |||
743 | @@ -0,0 +1,86 @@ | |||
744 | 1 | # -*- coding: utf-8 -*- | ||
745 | 2 | ############################################################################## | ||
746 | 3 | # | ||
747 | 4 | # Copyright (C) 2013 Daniel Reis | ||
748 | 5 | # | ||
749 | 6 | # This program is free software: you can redistribute it and/or modify | ||
750 | 7 | # it under the terms of the GNU Affero General Public License as | ||
751 | 8 | # published by the Free Software Foundation, either version 3 of the | ||
752 | 9 | # License, or (at your option) any later version. | ||
753 | 10 | # | ||
754 | 11 | # This program is distributed in the hope that it will be useful, | ||
755 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
756 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
757 | 14 | # GNU Affero General Public License for more details. | ||
758 | 15 | # | ||
759 | 16 | # You should have received a copy of the GNU Affero General Public License | ||
760 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
761 | 18 | # | ||
762 | 19 | ############################################################################## | ||
763 | 20 | |||
764 | 21 | from openerp.osv import fields, orm | ||
765 | 22 | |||
766 | 23 | |||
767 | 24 | class SLADefinition(orm.Model): | ||
768 | 25 | """ | ||
769 | 26 | SLA Definition | ||
770 | 27 | """ | ||
771 | 28 | _name = 'project.sla' | ||
772 | 29 | _description = 'SLA Definition' | ||
773 | 30 | _columns = { | ||
774 | 31 | 'name': fields.char('Title', size=64, required=True, translate=True), | ||
775 | 32 | 'active': fields.boolean('Active'), | ||
776 | 33 | 'control_model': fields.char('For documents', size=128, required=True), | ||
777 | 34 | 'control_field_id': fields.many2one( | ||
778 | 35 | 'ir.model.fields', 'Control Date', required=True, | ||
779 | 36 | domain="[('model_id.model', '=', control_model)," | ||
780 | 37 | " ('ttype', 'in', ['date', 'datetime'])]", | ||
781 | 38 | help="Date field used to check if the SLA was achieved."), | ||
782 | 39 | 'sla_line_ids': fields.one2many( | ||
783 | 40 | 'project.sla.line', 'sla_id', 'Definitions'), | ||
784 | 41 | 'analytic_ids': fields.many2many( | ||
785 | 42 | 'account.analytic.account', string='Contracts'), | ||
786 | 43 | } | ||
787 | 44 | _defaults = { | ||
788 | 45 | 'active': True, | ||
789 | 46 | } | ||
790 | 47 | |||
791 | 48 | def _reapply_slas(self, cr, uid, ids, recalc_closed=False, context=None): | ||
792 | 49 | """ | ||
793 | 50 | Force SLA recalculation on all _open_ Contracts for the selected SLAs. | ||
794 | 51 | To use upon SLA Definition modifications. | ||
795 | 52 | """ | ||
796 | 53 | contract_obj = self.pool.get('account.analytic.account') | ||
797 | 54 | for sla in self.browse(cr, uid, ids, context=context): | ||
798 | 55 | contr_ids = [x.id for x in sla.analytic_ids if x.state == 'open'] | ||
799 | 56 | contract_obj._reapply_sla( | ||
800 | 57 | cr, uid, contr_ids, recalc_closed=recalc_closed, | ||
801 | 58 | context=context) | ||
802 | 59 | return True | ||
803 | 60 | |||
804 | 61 | def reapply_slas(self, cr, uid, ids, context=None): | ||
805 | 62 | """ Reapply SLAs button action """ | ||
806 | 63 | return self._reapply_slas(cr, uid, ids, context=context) | ||
807 | 64 | |||
808 | 65 | |||
809 | 66 | class SLARules(orm.Model): | ||
810 | 67 | """ | ||
811 | 68 | SLA Definition Rule Lines | ||
812 | 69 | """ | ||
813 | 70 | _name = 'project.sla.line' | ||
814 | 71 | _definition = 'SLA Definition Rule Lines' | ||
815 | 72 | _order = 'sla_id,sequence' | ||
816 | 73 | _columns = { | ||
817 | 74 | 'sla_id': fields.many2one('project.sla', 'SLA Definition'), | ||
818 | 75 | 'sequence': fields.integer('Sequence'), | ||
819 | 76 | 'name': fields.char('Title', size=64, required=True, translate=True), | ||
820 | 77 | 'condition': fields.char( | ||
821 | 78 | 'Condition', size=256, help="Apply only if this expression is " | ||
822 | 79 | "evaluated to True. The document fields can be accessed using " | ||
823 | 80 | "either o, obj or object. Example: obj.priority <= '2'"), | ||
824 | 81 | 'limit_qty': fields.integer('Hours to Limit'), | ||
825 | 82 | 'warn_qty': fields.integer('Hours to Warn'), | ||
826 | 83 | } | ||
827 | 84 | _defaults = { | ||
828 | 85 | 'sequence': 10, | ||
829 | 86 | } | ||
830 | 0 | 87 | ||
831 | === added file 'project_sla/project_sla_control.py' | |||
832 | --- project_sla/project_sla_control.py 1970-01-01 00:00:00 +0000 | |||
833 | +++ project_sla/project_sla_control.py 2013-12-26 15:27:30 +0000 | |||
834 | @@ -0,0 +1,322 @@ | |||
835 | 1 | # -*- coding: utf-8 -*- | ||
836 | 2 | ############################################################################## | ||
837 | 3 | # | ||
838 | 4 | # Copyright (C) 2013 Daniel Reis | ||
839 | 5 | # | ||
840 | 6 | # This program is free software: you can redistribute it and/or modify | ||
841 | 7 | # it under the terms of the GNU Affero General Public License as | ||
842 | 8 | # published by the Free Software Foundation, either version 3 of the | ||
843 | 9 | # License, or (at your option) any later version. | ||
844 | 10 | # | ||
845 | 11 | # This program is distributed in the hope that it will be useful, | ||
846 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
847 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
848 | 14 | # GNU Affero General Public License for more details. | ||
849 | 15 | # | ||
850 | 16 | # You should have received a copy of the GNU Affero General Public License | ||
851 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
852 | 18 | # | ||
853 | 19 | ############################################################################## | ||
854 | 20 | |||
855 | 21 | from openerp.osv import fields, orm | ||
856 | 22 | from openerp.tools.safe_eval import safe_eval | ||
857 | 23 | from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT as DT_FMT | ||
858 | 24 | from openerp import SUPERUSER_ID | ||
859 | 25 | from datetime import timedelta | ||
860 | 26 | from datetime import datetime as dt | ||
861 | 27 | import m2m | ||
862 | 28 | |||
863 | 29 | import logging | ||
864 | 30 | _logger = logging.getLogger(__name__) | ||
865 | 31 | |||
866 | 32 | |||
867 | 33 | SLA_STATES = [('5', 'Failed'), ('4', 'Will Fail'), ('3', 'Warning'), | ||
868 | 34 | ('2', 'Watching'), ('1', 'Achieved')] | ||
869 | 35 | |||
870 | 36 | |||
871 | 37 | def safe_getattr(obj, dotattr, default=False): | ||
872 | 38 | """ | ||
873 | 39 | Follow an object attribute dot-notation chain to find the leaf value. | ||
874 | 40 | If any attribute doesn't exist or has no value, just return False. | ||
875 | 41 | Checks hasattr ahead, to avoid ORM Browse log warnings. | ||
876 | 42 | """ | ||
877 | 43 | attrs = dotattr.split('.') | ||
878 | 44 | while attrs: | ||
879 | 45 | attr = attrs.pop(0) | ||
880 | 46 | if attr in obj._model._columns: | ||
881 | 47 | try: | ||
882 | 48 | obj = getattr(obj, attr) | ||
883 | 49 | except AttributeError: | ||
884 | 50 | return default | ||
885 | 51 | if not obj: | ||
886 | 52 | return default | ||
887 | 53 | else: | ||
888 | 54 | return default | ||
889 | 55 | return obj | ||
890 | 56 | |||
891 | 57 | |||
892 | 58 | class SLAControl(orm.Model): | ||
893 | 59 | """ | ||
894 | 60 | SLA Control Registry | ||
895 | 61 | Each controlled document (Issue, Claim, ...) will have a record here. | ||
896 | 62 | This model concentrates all the logic for Service Level calculation. | ||
897 | 63 | """ | ||
898 | 64 | _name = 'project.sla.control' | ||
899 | 65 | _description = 'SLA Control Registry' | ||
900 | 66 | |||
901 | 67 | _columns = { | ||
902 | 68 | 'doc_id': fields.integer('Document ID', readonly=True), | ||
903 | 69 | 'doc_model': fields.char('Document Model', size=128, readonly=True), | ||
904 | 70 | 'sla_line_id': fields.many2one( | ||
905 | 71 | 'project.sla.line', 'Service Agreement'), | ||
906 | 72 | 'sla_warn_date': fields.datetime('Warning Date'), | ||
907 | 73 | 'sla_limit_date': fields.datetime('Limit Date'), | ||
908 | 74 | 'sla_start_date': fields.datetime('Start Date'), | ||
909 | 75 | 'sla_close_date': fields.datetime('Close Date'), | ||
910 | 76 | 'sla_achieved': fields.integer('Achieved?'), | ||
911 | 77 | 'sla_state': fields.selection(SLA_STATES, string="SLA Status"), | ||
912 | 78 | 'locked': fields.boolean( | ||
913 | 79 | 'Recalculation disabled', | ||
914 | 80 | help="Safeguard manual changes from future automatic " | ||
915 | 81 | "recomputations."), | ||
916 | 82 | # Future: perfect SLA manual handling | ||
917 | 83 | } | ||
918 | 84 | |||
919 | 85 | def write(self, cr, uid, ids, vals, context=None): | ||
920 | 86 | """ | ||
921 | 87 | Update the related Document's SLA State when any of the SLA Control | ||
922 | 88 | lines changes state | ||
923 | 89 | """ | ||
924 | 90 | res = super(SLAControl, self).write( | ||
925 | 91 | cr, uid, ids, vals, context=context) | ||
926 | 92 | new_state = vals.get('sla_state') | ||
927 | 93 | if new_state: | ||
928 | 94 | # just update sla_state without recomputing the whole thing | ||
929 | 95 | context = context or {} | ||
930 | 96 | context['__sla_stored__'] = 1 | ||
931 | 97 | for sla in self.browse(cr, uid, ids, context=context): | ||
932 | 98 | doc = self.pool.get(sla.doc_model).browse( | ||
933 | 99 | cr, uid, sla.doc_id, context=context) | ||
934 | 100 | if doc.sla_state < new_state: | ||
935 | 101 | doc.write({'sla_state': new_state}) | ||
936 | 102 | return res | ||
937 | 103 | |||
938 | 104 | def update_sla_states(self, cr, uid, context=None): | ||
939 | 105 | """ | ||
940 | 106 | Updates SLA States, given the current datetime: | ||
941 | 107 | Only works on "open" sla states (watching, warning and will fail): | ||
942 | 108 | - exceeded limit date are set to "will fail" | ||
943 | 109 | - exceeded warning dates are set to "warning" | ||
944 | 110 | To be used by a scheduled job. | ||
945 | 111 | """ | ||
946 | 112 | now = dt.now().strftime(DT_FMT) | ||
947 | 113 | # SLAs to mark as "will fail" | ||
948 | 114 | control_ids = self.search( | ||
949 | 115 | cr, uid, | ||
950 | 116 | [('sla_state', 'in', ['2', '3']), ('sla_limit_date', '<', now)], | ||
951 | 117 | context=context) | ||
952 | 118 | self.write(cr, uid, control_ids, {'sla_state': '4'}, context=context) | ||
953 | 119 | # SLAs to mark as "warning" | ||
954 | 120 | control_ids = self.search( | ||
955 | 121 | cr, uid, | ||
956 | 122 | [('sla_state', 'in', ['2']), ('sla_warn_date', '<', now)], | ||
957 | 123 | context=context) | ||
958 | 124 | self.write(cr, uid, control_ids, {'sla_state': '3'}, context=context) | ||
959 | 125 | return True | ||
960 | 126 | |||
961 | 127 | def _compute_sla_date(self, cr, uid, working_hours, res_uid, | ||
962 | 128 | start_date, hours, context=None): | ||
963 | 129 | """ | ||
964 | 130 | Return a limit datetime by adding hours to a start_date, honoring | ||
965 | 131 | a working_time calendar and a resource's (res_uid) timezone and | ||
966 | 132 | availability (leaves) | ||
967 | 133 | |||
968 | 134 | Currently implemented using a binary search using | ||
969 | 135 | _interval_hours_get() from resource.calendar. This is | ||
970 | 136 | resource.calendar agnostic, but could be more efficient if | ||
971 | 137 | implemented based on it's logic. | ||
972 | 138 | |||
973 | 139 | Known issue: the end date can be a non-working time; it would be | ||
974 | 140 | best for it to be the latest working time possible. Example: | ||
975 | 141 | if working time is 08:00 - 16:00 and start_date is 19:00, the +8h | ||
976 | 142 | end date will be 19:00 of the next day, and it should rather be | ||
977 | 143 | 16:00 of the next day. | ||
978 | 144 | """ | ||
979 | 145 | assert isinstance(start_date, dt) | ||
980 | 146 | assert isinstance(hours, int) and hours >= 0 | ||
981 | 147 | |||
982 | 148 | cal_obj = self.pool.get('resource.calendar') | ||
983 | 149 | target, step = hours * 3600, 16 * 3600 | ||
984 | 150 | lo, hi = start_date, start_date | ||
985 | 151 | while target > 0 and step > 60: | ||
986 | 152 | hi = lo + timedelta(seconds=step) | ||
987 | 153 | check = int(3600 * cal_obj._interval_hours_get( | ||
988 | 154 | cr, uid, working_hours, lo, hi, | ||
989 | 155 | timezone_from_uid=res_uid, exclude_leaves=False, | ||
990 | 156 | context=context)) | ||
991 | 157 | if check <= target: | ||
992 | 158 | target -= check | ||
993 | 159 | lo = hi | ||
994 | 160 | else: | ||
995 | 161 | step = int(step / 4.0) | ||
996 | 162 | return hi | ||
997 | 163 | |||
998 | 164 | def _get_computed_slas(self, cr, uid, doc, context=None): | ||
999 | 165 | """ | ||
1000 | 166 | Returns a dict with the computed data for SLAs, given a browse record | ||
1001 | 167 | for the target document. | ||
1002 | 168 | |||
1003 | 169 | * The SLA used is either from a related analytic_account_id or | ||
1004 | 170 | project_id, whatever is found first. | ||
1005 | 171 | * The work calendar is taken from the Project's definitions ("Other | ||
1006 | 172 | Info" tab -> Working Time). | ||
1007 | 173 | * The timezone used for the working time calculations are from the | ||
1008 | 174 | document's responsible User (user_id) or from the current User (uid). | ||
1009 | 175 | |||
1010 | 176 | For the SLA Achieved calculation: | ||
1011 | 177 | |||
1012 | 178 | * Creation date is used to start counting time | ||
1013 | 179 | * Control date, used to calculate SLA achievement, is defined in the | ||
1014 | 180 | SLA Definition rules. | ||
1015 | 181 | """ | ||
1016 | 182 | def datetime2str(dt_value, fmt): # tolerant datetime to string | ||
1017 | 183 | return dt_value and dt.strftime(dt_value, fmt) or None | ||
1018 | 184 | |||
1019 | 185 | res = [] | ||
1020 | 186 | sla_ids = (safe_getattr(doc, 'analytic_account_id.sla_ids') or | ||
1021 | 187 | safe_getattr(doc, 'project_id.analytic_account_id.sla_ids')) | ||
1022 | 188 | if not sla_ids: | ||
1023 | 189 | return res | ||
1024 | 190 | |||
1025 | 191 | for sla in sla_ids: | ||
1026 | 192 | if sla.control_model != doc._table_name: | ||
1027 | 193 | continue # SLA not for this model; skip | ||
1028 | 194 | |||
1029 | 195 | for l in sla.sla_line_ids: | ||
1030 | 196 | eval_context = {'o': doc, 'obj': doc, 'object': doc} | ||
1031 | 197 | if not l.condition or safe_eval(l.condition, eval_context): | ||
1032 | 198 | start_date = dt.strptime(doc.create_date, DT_FMT) | ||
1033 | 199 | res_uid = doc.user_id.id or uid | ||
1034 | 200 | cal = safe_getattr( | ||
1035 | 201 | doc, 'project_id.resource_calendar_id.id') | ||
1036 | 202 | warn_date = self._compute_sla_date( | ||
1037 | 203 | cr, uid, cal, res_uid, start_date, l.warn_qty, | ||
1038 | 204 | context=context) | ||
1039 | 205 | lim_date = self._compute_sla_date( | ||
1040 | 206 | cr, uid, cal, res_uid, warn_date, | ||
1041 | 207 | l.limit_qty - l.warn_qty, | ||
1042 | 208 | context=context) | ||
1043 | 209 | # evaluate sla state | ||
1044 | 210 | control_val = getattr(doc, sla.control_field_id.name) | ||
1045 | 211 | if control_val: | ||
1046 | 212 | control_date = dt.strptime(control_val, DT_FMT) | ||
1047 | 213 | if control_date > lim_date: | ||
1048 | 214 | sla_val, sla_state = 0, '5' # failed | ||
1049 | 215 | else: | ||
1050 | 216 | sla_val, sla_state = 1, '1' # achieved | ||
1051 | 217 | else: | ||
1052 | 218 | control_date = None | ||
1053 | 219 | now = dt.now() | ||
1054 | 220 | if now > lim_date: | ||
1055 | 221 | sla_val, sla_state = 0, '4' # will fail | ||
1056 | 222 | elif now > warn_date: | ||
1057 | 223 | sla_val, sla_state = 0, '3' # warning | ||
1058 | 224 | else: | ||
1059 | 225 | sla_val, sla_state = 0, '2' # watching | ||
1060 | 226 | |||
1061 | 227 | res.append( | ||
1062 | 228 | {'sla_line_id': l.id, | ||
1063 | 229 | 'sla_achieved': sla_val, | ||
1064 | 230 | 'sla_state': sla_state, | ||
1065 | 231 | 'sla_warn_date': datetime2str(warn_date, DT_FMT), | ||
1066 | 232 | 'sla_limit_date': datetime2str(lim_date, DT_FMT), | ||
1067 | 233 | 'sla_start_date': datetime2str(start_date, DT_FMT), | ||
1068 | 234 | 'sla_close_date': datetime2str(control_date, DT_FMT), | ||
1069 | 235 | 'doc_id': doc.id, | ||
1070 | 236 | 'doc_model': sla.control_model}) | ||
1071 | 237 | break | ||
1072 | 238 | |||
1073 | 239 | if sla_ids and not res: | ||
1074 | 240 | _logger.warning("No valid SLA rule found for %d, SLA Ids %s" | ||
1075 | 241 | % (doc.id, repr([x.id for x in sla_ids]))) | ||
1076 | 242 | return res | ||
1077 | 243 | |||
1078 | 244 | def store_sla_control(self, cr, uid, docs, context=None): | ||
1079 | 245 | """ | ||
1080 | 246 | Used by controlled documents to ask for SLA calculation and storage. | ||
1081 | 247 | ``docs`` is a Browse object | ||
1082 | 248 | """ | ||
1083 | 249 | # context flag to avoid infinite loops on further writes | ||
1084 | 250 | context = context or {} | ||
1085 | 251 | if '__sla_stored__' in context: | ||
1086 | 252 | return False | ||
1087 | 253 | else: | ||
1088 | 254 | context['__sla_stored__'] = 1 | ||
1089 | 255 | |||
1090 | 256 | res = [] | ||
1091 | 257 | for ix, doc in enumerate(docs): | ||
1092 | 258 | if ix and ix % 50 == 0: | ||
1093 | 259 | _logger.info('...%d SLAs recomputed for %s' % (ix, doc._name)) | ||
1094 | 260 | control = {x.sla_line_id.id: x | ||
1095 | 261 | for x in doc.sla_control_ids} | ||
1096 | 262 | sla_recs = self._get_computed_slas(cr, uid, doc, context=context) | ||
1097 | 263 | # calc sla control lines | ||
1098 | 264 | if sla_recs: | ||
1099 | 265 | slas = [] | ||
1100 | 266 | for sla_rec in sla_recs: | ||
1101 | 267 | sla_line_id = sla_rec.get('sla_line_id') | ||
1102 | 268 | if sla_line_id in control: | ||
1103 | 269 | control_rec = control.get(sla_line_id) | ||
1104 | 270 | if not control_rec.locked: | ||
1105 | 271 | slas += m2m.write(control_rec.id, sla_rec) | ||
1106 | 272 | else: | ||
1107 | 273 | slas += m2m.add(sla_rec) | ||
1108 | 274 | else: | ||
1109 | 275 | slas = m2m.clear() | ||
1110 | 276 | # calc sla control summary | ||
1111 | 277 | vals = {'sla_state': None, 'sla_control_ids': slas} | ||
1112 | 278 | if sla_recs and doc.sla_control_ids: | ||
1113 | 279 | vals['sla_state'] = max( | ||
1114 | 280 | x.sla_state for x in doc.sla_control_ids) | ||
1115 | 281 | # store sla | ||
1116 | 282 | doc._model.write( # regular users can't write on SLA Control | ||
1117 | 283 | cr, SUPERUSER_ID, [doc.id], vals, context=context) | ||
1118 | 284 | return res | ||
1119 | 285 | |||
1120 | 286 | |||
1121 | 287 | class SLAControlled(orm.AbstractModel): | ||
1122 | 288 | """ | ||
1123 | 289 | SLA Controlled documents: AbstractModel to apply SLA control on Models | ||
1124 | 290 | """ | ||
1125 | 291 | _name = 'project.sla.controlled' | ||
1126 | 292 | _description = 'SLA Controlled Document' | ||
1127 | 293 | _columns = { | ||
1128 | 294 | 'sla_control_ids': fields.many2many( | ||
1129 | 295 | 'project.sla.control', string="SLA Control", ondelete='cascade'), | ||
1130 | 296 | 'sla_state': fields.selection( | ||
1131 | 297 | SLA_STATES, string="SLA Status", readonly=True), | ||
1132 | 298 | } | ||
1133 | 299 | |||
1134 | 300 | def create(self, cr, uid, vals, context=None): | ||
1135 | 301 | res = super(SLAControlled, self).create(cr, uid, vals, context=context) | ||
1136 | 302 | docs = self.browse(cr, uid, [res], context=context) | ||
1137 | 303 | self.pool.get('project.sla.control').store_sla_control( | ||
1138 | 304 | cr, uid, docs, context=context) | ||
1139 | 305 | return res | ||
1140 | 306 | |||
1141 | 307 | def write(self, cr, uid, ids, vals, context=None): | ||
1142 | 308 | res = super(SLAControlled, self).write( | ||
1143 | 309 | cr, uid, ids, vals, context=context) | ||
1144 | 310 | docs = [x for x in self.browse(cr, uid, ids, context=context) | ||
1145 | 311 | if (x.state != 'cancelled') and | ||
1146 | 312 | (x.state != 'done' or x.sla_state not in ['1', '5'])] | ||
1147 | 313 | self.pool.get('project.sla.control').store_sla_control( | ||
1148 | 314 | cr, uid, docs, context=context) | ||
1149 | 315 | return res | ||
1150 | 316 | |||
1151 | 317 | def unlink(self, cr, uid, ids, context=None): | ||
1152 | 318 | # Unlink and delete all related Control records | ||
1153 | 319 | for doc in self.browse(cr, uid, ids, context=context): | ||
1154 | 320 | vals = [m2m.remove(x.id)[0] for x in doc.sla_control_ids] | ||
1155 | 321 | doc.write({'sla_control_ids': vals}) | ||
1156 | 322 | return super(SLAControlled, self).unlink(cr, uid, ids, context=context) | ||
1157 | 0 | 323 | ||
1158 | === added file 'project_sla/project_sla_control_data.xml' | |||
1159 | --- project_sla/project_sla_control_data.xml 1970-01-01 00:00:00 +0000 | |||
1160 | +++ project_sla/project_sla_control_data.xml 2013-12-26 15:27:30 +0000 | |||
1161 | @@ -0,0 +1,18 @@ | |||
1162 | 1 | <?xml version="1.0" encoding="utf-8"?> | ||
1163 | 2 | <openerp> | ||
1164 | 3 | <data noupdate="1"> | ||
1165 | 4 | |||
1166 | 5 | <record id="ir_cron_sla_action" model="ir.cron"> | ||
1167 | 6 | <field name="name">Update SLA States</field> | ||
1168 | 7 | <field name="priority" eval="100"/> | ||
1169 | 8 | <field name="interval_number">1</field> | ||
1170 | 9 | <field name="interval_type">hours</field> | ||
1171 | 10 | <field name="numbercall">-1</field> | ||
1172 | 11 | <field name="doall" eval="False"/> | ||
1173 | 12 | <field name="model">project.sla.control</field> | ||
1174 | 13 | <field name="function">update_sla_states</field> | ||
1175 | 14 | <field name="args">()</field> | ||
1176 | 15 | </record> | ||
1177 | 16 | |||
1178 | 17 | </data> | ||
1179 | 18 | </openerp> | ||
1180 | 0 | 19 | ||
1181 | === added file 'project_sla/project_sla_control_view.xml' | |||
1182 | --- project_sla/project_sla_control_view.xml 1970-01-01 00:00:00 +0000 | |||
1183 | +++ project_sla/project_sla_control_view.xml 2013-12-26 15:27:30 +0000 | |||
1184 | @@ -0,0 +1,25 @@ | |||
1185 | 1 | <?xml version="1.0" encoding="utf-8"?> | ||
1186 | 2 | <openerp> | ||
1187 | 3 | <data> | ||
1188 | 4 | |||
1189 | 5 | <!-- List view used when the sla_control_ids field | ||
1190 | 6 | is added to controlled document's form --> | ||
1191 | 7 | <record id="view_sla_control_tree" model="ir.ui.view"> | ||
1192 | 8 | <field name="name">view_sla_control_tree</field> | ||
1193 | 9 | <field name="model">project.sla.control</field> | ||
1194 | 10 | <field name="arch" type="xml"> | ||
1195 | 11 | |||
1196 | 12 | <tree string="Service Level"> | ||
1197 | 13 | <field name="sla_line_id"/> | ||
1198 | 14 | <field name="sla_state"/> | ||
1199 | 15 | <field name="sla_start_date"/> | ||
1200 | 16 | <field name="sla_warn_date"/> | ||
1201 | 17 | <field name="sla_limit_date"/> | ||
1202 | 18 | <field name="sla_close_date"/> | ||
1203 | 19 | </tree> | ||
1204 | 20 | |||
1205 | 21 | </field> | ||
1206 | 22 | </record> | ||
1207 | 23 | |||
1208 | 24 | </data> | ||
1209 | 25 | </openerp> | ||
1210 | 0 | 26 | ||
1211 | === added file 'project_sla/project_sla_demo.xml' | |||
1212 | --- project_sla/project_sla_demo.xml 1970-01-01 00:00:00 +0000 | |||
1213 | +++ project_sla/project_sla_demo.xml 2013-12-26 15:27:30 +0000 | |||
1214 | @@ -0,0 +1,138 @@ | |||
1215 | 1 | <?xml version="1.0" encoding="utf-8"?> | ||
1216 | 2 | <openerp> | ||
1217 | 3 | <data> | ||
1218 | 4 | |||
1219 | 5 | <!-- Working Time calendar --> | ||
1220 | 6 | <record id="worktime_9_18" model="resource.calendar"> | ||
1221 | 7 | <field name="name">Working Days 09-13 14-18</field> | ||
1222 | 8 | </record> | ||
1223 | 9 | <record id="worktime 9_18_0M" model="resource.calendar.attendance"> | ||
1224 | 10 | <field name="dayofweek">0</field> | ||
1225 | 11 | <field name="name">Monday Morning</field> | ||
1226 | 12 | <field name="hour_from">9</field> | ||
1227 | 13 | <field name="hour_to">13</field> | ||
1228 | 14 | <field name="calendar_id" ref="worktime_9_18" /> | ||
1229 | 15 | </record> | ||
1230 | 16 | <record id="worktime 9_18_0A" model="resource.calendar.attendance"> | ||
1231 | 17 | <field name="dayofweek">0</field> | ||
1232 | 18 | <field name="name">Monday Afternoon</field> | ||
1233 | 19 | <field name="hour_from">14</field> | ||
1234 | 20 | <field name="hour_to">18</field> | ||
1235 | 21 | <field name="calendar_id" ref="worktime_9_18" /> | ||
1236 | 22 | </record> | ||
1237 | 23 | <record id="worktime 9_18_1M" model="resource.calendar.attendance"> | ||
1238 | 24 | <field name="dayofweek">1</field> | ||
1239 | 25 | <field name="name">Tuesday Morning</field> | ||
1240 | 26 | <field name="hour_from">9</field> | ||
1241 | 27 | <field name="hour_to">13</field> | ||
1242 | 28 | <field name="calendar_id" ref="worktime_9_18" /> | ||
1243 | 29 | </record> | ||
1244 | 30 | <record id="worktime 9_18_1A" model="resource.calendar.attendance"> | ||
1245 | 31 | <field name="dayofweek">1</field> | ||
1246 | 32 | <field name="name">Tuesday Afternoon</field> | ||
1247 | 33 | <field name="hour_from">14</field> | ||
1248 | 34 | <field name="hour_to">18</field> | ||
1249 | 35 | <field name="calendar_id" ref="worktime_9_18" /> | ||
1250 | 36 | </record> | ||
1251 | 37 | <record id="worktime 9_18_2M" model="resource.calendar.attendance"> | ||
1252 | 38 | <field name="dayofweek">2</field> | ||
1253 | 39 | <field name="name">Wednesday Morning</field> | ||
1254 | 40 | <field name="hour_from">9</field> | ||
1255 | 41 | <field name="hour_to">13</field> | ||
1256 | 42 | <field name="calendar_id" ref="worktime_9_18" /> | ||
1257 | 43 | </record> | ||
1258 | 44 | <record id="worktime 9_18_2A" model="resource.calendar.attendance"> | ||
1259 | 45 | <field name="dayofweek">2</field> | ||
1260 | 46 | <field name="name">Wednesday Afternoon</field> | ||
1261 | 47 | <field name="hour_from">14</field> | ||
1262 | 48 | <field name="hour_to">18</field> | ||
1263 | 49 | <field name="calendar_id" ref="worktime_9_18" /> | ||
1264 | 50 | </record> | ||
1265 | 51 | <record id="worktime 9_18_3M" model="resource.calendar.attendance"> | ||
1266 | 52 | <field name="dayofweek">3</field> | ||
1267 | 53 | <field name="name">Thursday Morning</field> | ||
1268 | 54 | <field name="hour_from">9</field> | ||
1269 | 55 | <field name="hour_to">13</field> | ||
1270 | 56 | <field name="calendar_id" ref="worktime_9_18" /> | ||
1271 | 57 | </record> | ||
1272 | 58 | <record id="worktime 9_18_3A" model="resource.calendar.attendance"> | ||
1273 | 59 | <field name="dayofweek">3</field> | ||
1274 | 60 | <field name="name">Thursday Afternoon</field> | ||
1275 | 61 | <field name="hour_from">14</field> | ||
1276 | 62 | <field name="hour_to">18</field> | ||
1277 | 63 | <field name="calendar_id" ref="worktime_9_18" /> | ||
1278 | 64 | </record> | ||
1279 | 65 | <record id="worktime 9_18_4M" model="resource.calendar.attendance"> | ||
1280 | 66 | <field name="dayofweek">4</field> | ||
1281 | 67 | <field name="name">Friday Morning</field> | ||
1282 | 68 | <field name="hour_from">9</field> | ||
1283 | 69 | <field name="hour_to">13</field> | ||
1284 | 70 | <field name="calendar_id" ref="worktime_9_18" /> | ||
1285 | 71 | </record> | ||
1286 | 72 | <record id="worktime 9_18_4A" model="resource.calendar.attendance"> | ||
1287 | 73 | <field name="dayofweek">4</field> | ||
1288 | 74 | <field name="name">Friday Afternoon</field> | ||
1289 | 75 | <field name="hour_from">14</field> | ||
1290 | 76 | <field name="hour_to">18</field> | ||
1291 | 77 | <field name="calendar_id" ref="worktime_9_18" /> | ||
1292 | 78 | </record> | ||
1293 | 79 | |||
1294 | 80 | <!-- Set Project Calendar --> | ||
1295 | 81 | <record id="project.project_project_1" model="project.project"> | ||
1296 | 82 | <field name="resource_calendar_id" ref="worktime_9_18" /> | ||
1297 | 83 | </record> | ||
1298 | 84 | |||
1299 | 85 | <!-- SLA Definition and Rules --> | ||
1300 | 86 | <record id="sla_resolution" model="project.sla"> | ||
1301 | 87 | <field name="name">Standard Resolution Time</field> | ||
1302 | 88 | <field name="control_model">project.issue</field> | ||
1303 | 89 | <field name="control_field_id" | ||
1304 | 90 | ref="project_issue.field_project_issue_date_closed"/> | ||
1305 | 91 | </record> | ||
1306 | 92 | <record id="sla_resolution_rule1" model="project.sla.line"> | ||
1307 | 93 | <field name="sla_id" ref="sla_resolution"/> | ||
1308 | 94 | <field name="sequence">10</field> | ||
1309 | 95 | <field name="name">Resolution in two business days</field> | ||
1310 | 96 | <field name="condition">obj.priority <= '2'</field> | ||
1311 | 97 | <field name="limit_qty">16</field> | ||
1312 | 98 | <field name="warn_qty">8</field> | ||
1313 | 99 | </record> | ||
1314 | 100 | <record id="sla_resolution_rule2" model="project.sla.line"> | ||
1315 | 101 | <field name="sla_id" ref="sla_resolution"/> | ||
1316 | 102 | <field name="sequence">20</field> | ||
1317 | 103 | <field name="name">Resolution in three business days</field> | ||
1318 | 104 | <field name="condition"></field> | ||
1319 | 105 | <field name="limit_qty">24</field> | ||
1320 | 106 | <field name="warn_qty">16</field> | ||
1321 | 107 | </record> | ||
1322 | 108 | |||
1323 | 109 | <record id="sla_response" model="project.sla"> | ||
1324 | 110 | <field name="name">Standard Response Time</field> | ||
1325 | 111 | <field name="control_model">project.issue</field> | ||
1326 | 112 | <field name="control_field_id" | ||
1327 | 113 | ref="project_issue.field_project_issue_date_open"/> | ||
1328 | 114 | </record> | ||
1329 | 115 | <record id="sla_response_rule1" model="project.sla.line"> | ||
1330 | 116 | <field name="sla_id" ref="sla_response"/> | ||
1331 | 117 | <field name="sequence">10</field> | ||
1332 | 118 | <field name="name">Response in one business day</field> | ||
1333 | 119 | <field name="condition">obj.priority <= '2'</field> | ||
1334 | 120 | <field name="limit_qty">8</field> | ||
1335 | 121 | <field name="warn_qty">4</field> | ||
1336 | 122 | </record> | ||
1337 | 123 | <record id="sla_response_rule2" model="project.sla.line"> | ||
1338 | 124 | <field name="sla_id" ref="sla_response"/> | ||
1339 | 125 | <field name="sequence">20</field> | ||
1340 | 126 | <field name="name">Response in two business days</field> | ||
1341 | 127 | <field name="condition"></field> | ||
1342 | 128 | <field name="limit_qty">16</field> | ||
1343 | 129 | <field name="warn_qty">8</field> | ||
1344 | 130 | </record> | ||
1345 | 131 | |||
1346 | 132 | <!-- Set Contract Resolution SLA Definition --> | ||
1347 | 133 | <record id="project.project_project_1_account_analytic_account" model="account.analytic.account"> | ||
1348 | 134 | <field name="sla_ids" eval="[(6, 0, [ref('sla_resolution')])]" /> | ||
1349 | 135 | </record> | ||
1350 | 136 | |||
1351 | 137 | </data> | ||
1352 | 138 | </openerp> | ||
1353 | 0 | 139 | ||
1354 | === added file 'project_sla/project_sla_view.xml' | |||
1355 | --- project_sla/project_sla_view.xml 1970-01-01 00:00:00 +0000 | |||
1356 | +++ project_sla/project_sla_view.xml 2013-12-26 15:27:30 +0000 | |||
1357 | @@ -0,0 +1,48 @@ | |||
1358 | 1 | <?xml version="1.0" encoding="utf-8"?> | ||
1359 | 2 | <openerp> | ||
1360 | 3 | <data> | ||
1361 | 4 | |||
1362 | 5 | <record id="view_sla_lines_tree" model="ir.ui.view"> | ||
1363 | 6 | <field name="name">view_sla_lines_tree</field> | ||
1364 | 7 | <field name="model">project.sla.line</field> | ||
1365 | 8 | <field name="arch" type="xml"> | ||
1366 | 9 | |||
1367 | 10 | <tree string="Definitions"> | ||
1368 | 11 | <field name="sequence"/> | ||
1369 | 12 | <field name="name"/> | ||
1370 | 13 | <field name="condition"/> | ||
1371 | 14 | <field name="limit_qty"/> | ||
1372 | 15 | <field name="warn_qty"/> | ||
1373 | 16 | </tree> | ||
1374 | 17 | |||
1375 | 18 | </field> | ||
1376 | 19 | </record> | ||
1377 | 20 | |||
1378 | 21 | <record id="view_sla_form" model="ir.ui.view"> | ||
1379 | 22 | <field name="name">view_sla_form</field> | ||
1380 | 23 | <field name="model">project.sla</field> | ||
1381 | 24 | <field name="arch" type="xml"> | ||
1382 | 25 | |||
1383 | 26 | <form string="SLA Definition"> | ||
1384 | 27 | <field name="name"/> | ||
1385 | 28 | <field name="active"/> | ||
1386 | 29 | <field name="control_model"/> | ||
1387 | 30 | <field name="control_field_id"/> | ||
1388 | 31 | <notebook colspan="4"> | ||
1389 | 32 | <page string="Rules" name="rules_page"> | ||
1390 | 33 | <field name="sla_line_ids" nolabel="1"/> | ||
1391 | 34 | </page> | ||
1392 | 35 | <page string="Contracts" name="contracts_page"> | ||
1393 | 36 | <field name="analytic_ids" nolabel="1" /> | ||
1394 | 37 | </page> | ||
1395 | 38 | </notebook> | ||
1396 | 39 | <button name="reapply_slas" colspan="2" | ||
1397 | 40 | string="Reapply SLA on Contracts" | ||
1398 | 41 | type="object" /> | ||
1399 | 42 | </form> | ||
1400 | 43 | |||
1401 | 44 | </field> | ||
1402 | 45 | </record> | ||
1403 | 46 | |||
1404 | 47 | </data> | ||
1405 | 48 | </openerp> | ||
1406 | 0 | 49 | ||
1407 | === added file 'project_sla/project_view.xml' | |||
1408 | --- project_sla/project_view.xml 1970-01-01 00:00:00 +0000 | |||
1409 | +++ project_sla/project_view.xml 2013-12-26 15:27:30 +0000 | |||
1410 | @@ -0,0 +1,20 @@ | |||
1411 | 1 | <?xml version="1.0" encoding="utf-8"?> | ||
1412 | 2 | <openerp> | ||
1413 | 3 | <data> | ||
1414 | 4 | |||
1415 | 5 | <record id="edit_project_sla" model="ir.ui.view"> | ||
1416 | 6 | <field name="name">edit_project_sla</field> | ||
1417 | 7 | <field name="model">project.project</field> | ||
1418 | 8 | <field name="inherit_id" ref="project.edit_project"/> | ||
1419 | 9 | <field name="arch" type="xml"> | ||
1420 | 10 | |||
1421 | 11 | <!-- make resource calendar always visible --> | ||
1422 | 12 | <group string="Administration" position="attributes"> | ||
1423 | 13 | <attribute name="groups"/> | ||
1424 | 14 | </group> | ||
1425 | 15 | |||
1426 | 16 | </field> | ||
1427 | 17 | </record> | ||
1428 | 18 | |||
1429 | 19 | </data> | ||
1430 | 20 | </openerp> | ||
1431 | 0 | 21 | ||
1432 | === added directory 'project_sla/security' | |||
1433 | === added file 'project_sla/security/ir.model.access.csv' | |||
1434 | --- project_sla/security/ir.model.access.csv 1970-01-01 00:00:00 +0000 | |||
1435 | +++ project_sla/security/ir.model.access.csv 2013-12-26 15:27:30 +0000 | |||
1436 | @@ -0,0 +1,8 @@ | |||
1437 | 1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink | ||
1438 | 2 | access_sla_manager,access_sla_manager,model_project_sla,project.group_project_manager,1,1,1,1 | ||
1439 | 3 | access_sla_user,access_sla_user,model_project_sla,base.group_user,1,0,0,0 | ||
1440 | 4 | access_sla_lines_manager,access_sla_lines_manager,model_project_sla_line,project.group_project_manager,1,1,1,1 | ||
1441 | 5 | access_sla_lines_user,access_sla_lines_user,model_project_sla_line,base.group_user,1,0,0,0 | ||
1442 | 6 | access_sla_control_manager,access_sla_control_manager,model_project_sla_control,project.group_project_manager,1,1,0,0 | ||
1443 | 7 | access_sla_control_user,access_sla_control_user,model_project_sla_control,base.group_user,1,0,0,0 | ||
1444 | 8 | access_sla_controlled_manager,access_sla_controlled_manager,model_project_sla_controlled,project.group_project_manager,1,1,1,1 | ||
1445 | 0 | 9 | ||
1446 | === added directory 'project_sla/static' | |||
1447 | === added directory 'project_sla/static/src' | |||
1448 | === added directory 'project_sla/static/src/img' | |||
1449 | === added file 'project_sla/static/src/img/icon.png' | |||
1450 | 1 | Binary 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 | 10 | Binary 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 |
1451 | === added directory 'project_sla/test' | |||
1452 | === added file 'project_sla/test/project_sla.yml' | |||
1453 | --- project_sla/test/project_sla.yml 1970-01-01 00:00:00 +0000 | |||
1454 | +++ project_sla/test/project_sla.yml 2013-12-26 15:27:30 +0000 | |||
1455 | @@ -0,0 +1,66 @@ | |||
1456 | 1 | - | ||
1457 | 2 | Cleanup previous test run | ||
1458 | 3 | - | ||
1459 | 4 | !python {model: project.issue}: | | ||
1460 | 5 | res = self.search(cr, uid, [('name', '=', 'My monitor is flickering')]) | ||
1461 | 6 | self.unlink(cr, uid, res) | ||
1462 | 7 | - | ||
1463 | 8 | Create a new Issue | ||
1464 | 9 | - | ||
1465 | 10 | !record {model: project.issue, id: issue1, view: False}: | ||
1466 | 11 | name: "My monitor is flickering" | ||
1467 | 12 | project_id: project.project_project_1 | ||
1468 | 13 | priority: "3" | ||
1469 | 14 | user_id: base.user_root | ||
1470 | 15 | partner_id: base.res_partner_2 | ||
1471 | 16 | email_from: agr@agrolait.com | ||
1472 | 17 | categ_ids: | ||
1473 | 18 | - project_issue.project_issue_category_01 | ||
1474 | 19 | - | ||
1475 | 20 | Close the Issue | ||
1476 | 21 | - | ||
1477 | 22 | !python {model: project.issue}: | | ||
1478 | 23 | self.case_close(cr, uid, [ref("issue1")]) | ||
1479 | 24 | - | ||
1480 | 25 | Force the Issue's Create Date and Close Date | ||
1481 | 26 | Created friday before opening hour, closed on next monday near closing hour | ||
1482 | 27 | - | ||
1483 | 28 | !python {model: project.issue}: | | ||
1484 | 29 | import time | ||
1485 | 30 | self.write(cr, uid, [ref("issue1"),], { | ||
1486 | 31 | 'create_date': time.strftime('2013-11-22 06:15:00'), | ||
1487 | 32 | 'date_closed': time.strftime('2013-11-25 16:45:00'), | ||
1488 | 33 | }) | ||
1489 | 34 | - | ||
1490 | 35 | There should be Service Level info generated on the Issue | ||
1491 | 36 | - | ||
1492 | 37 | !assert {model: project.issue, id: issue1, string: Issue should have calculated service levels}: | ||
1493 | 38 | - len(sla_control_ids) == 2 | ||
1494 | 39 | - | ||
1495 | 40 | Assign an additional "Response SLA" to the Contract | ||
1496 | 41 | - | ||
1497 | 42 | !python {model: account.analytic.account}: | | ||
1498 | 43 | self.write(cr, uid, [ref('project.project_project_1_account_analytic_account')], | ||
1499 | 44 | {'sla_ids': [(4, ref('sla_response'))]}) | ||
1500 | 45 | - | ||
1501 | 46 | Button to Reapply the SLA Definition | ||
1502 | 47 | - | ||
1503 | 48 | !python {model: project.sla}: | | ||
1504 | 49 | self._reapply_slas(cr, uid, [ref('sla_resolution')], recalc_closed=True) | ||
1505 | 50 | - | ||
1506 | 51 | There should be two Service Level lines generated on the Issue | ||
1507 | 52 | - | ||
1508 | 53 | !assert {model: project.issue, id: issue1, string: Issue should have two calculated service levels}: | ||
1509 | 54 | - len(sla_control_ids) == 2 | ||
1510 | 55 | - | ||
1511 | 56 | The Issue's Resolution SLA should be "3 business days" | ||
1512 | 57 | - | ||
1513 | 58 | !python {model: project.issue}: | | ||
1514 | 59 | issue = self.browse(cr, uid, ref('issue1')) | ||
1515 | 60 | for x in issue.sla_control_ids: | ||
1516 | 61 | print x.sla_line_id.name | ||
1517 | 62 | if x.sla_line_id.id == ref("sla_resolution_rule2"): | ||
1518 | 63 | assert x.sla_achieved == 1, "Issue resolution SLA should be achieved" | ||
1519 | 64 | break | ||
1520 | 65 | else: | ||
1521 | 66 | assert False, 'Issue Resolution SLA should be "3 business days"' |
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.