Skip to content

Commit

Permalink
Merge pull request OCA#76 from katyukha/patch_70_3_fixes
Browse files Browse the repository at this point in the history
project_sla: code refactoring
  • Loading branch information
dreispt committed Feb 1, 2017
2 parents 95dfd87 + 21a3bbf commit 531255f
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 89 deletions.
3 changes: 2 additions & 1 deletion project_sla/__openerp__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
{
'name': 'Service Level Agreements',
'summary': 'Define SLAs for your Contracts',
'version': '1.0',
'version': '7.0.0.1.0',
"category": "Project Management",
'description': """\
Contract SLAs
Expand Down Expand Up @@ -130,4 +130,5 @@
'demo': ['project_sla_demo.xml'],
'test': ['test/project_sla.yml'],
'installable': True,
'license': 'AGPL-3',
}
52 changes: 33 additions & 19 deletions project_sla/analytic_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,31 +38,45 @@ def _reapply_sla(self, cr, uid, ids, recalc_closed=False, context=None):
The ``recalc_closed`` flag allows to also recompute closed documents.
"""
ctrl_obj = self.pool.get('project.sla.control')
sla_obj = self.pool.get('project.sla')

context = {} if context is None else context.copy()

exclude_states = ['cancelled', 'cancel']
if not recalc_closed:
exclude_states += ['done']

for contract in self.browse(cr, uid, ids, context=context):
# If this is called from SLA model, than we could reduce number of
# documents that need recalculation
if context.get('sla_ids', False):
sla_ids = context['sla_ids']
else:
sla_ids = sla_obj.search(cr, uid, [('analytic_ids', 'in', ids)],
context=context)

sla_recs = sla_obj.browse(cr, uid, sla_ids, context=context)
for m_name in set([sla.control_model for sla in sla_recs]):
# for each contract, and for each model under SLA control ...
for m_name in set([sla.control_model for sla in contract.sla_ids]):
model = self.pool.get(m_name)
doc_ids = []
if 'analytic_account_id' in model._columns:
doc_ids += model.search(
cr, uid,
[('analytic_account_id', '=', contract.id),
('state', 'not in', exclude_states)],
context=context)
if 'project_id' in model._columns:
doc_ids += model.search(
cr, uid,
[('project_id.analytic_account_id', '=', contract.id),
('state', 'not in', exclude_states)],
context=context)
if doc_ids:
docs = model.browse(cr, uid, doc_ids, context=context)
ctrl_obj.store_sla_control(cr, uid, docs, context=context)
model = self.pool.get(m_name)
domain = [('state', 'not in', exclude_states)]

# if model have both 'project_id' and 'analytic_account_id' fields,
# then we should use `OR` condition to search for both of these
# fields. Otherwise, search only by one of fields
if ('analytic_account_id' in model._columns and
'project_id' in model._columns):
domain += ['|', ('analytic_account_id', 'in', ids),
('project_id.analytic_account_id', 'in', ids)]
elif 'analytic_account_id' in model._columns:
domain += [('analytic_account_id', 'in', ids)]
elif 'project_id' in model._columns:
domain += [('project_id.analytic_account_id', 'in', ids)]

doc_ids = model.search(cr, uid, domain, context=context)

if doc_ids:
docs = model.browse(cr, uid, doc_ids, context=context)
ctrl_obj.store_sla_control(cr, uid, docs, context=context)
return True

def reapply_sla(self, cr, uid, ids, context=None):
Expand Down
1 change: 1 addition & 0 deletions project_sla/m2m.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
"""
Wrapper for OpenERP's cryptic write conventions for x2many fields.
Expand Down
15 changes: 10 additions & 5 deletions project_sla/project_sla.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,16 @@ def _reapply_slas(self, cr, uid, ids, recalc_closed=False, context=None):
To use upon SLA Definition modifications.
"""
contract_obj = self.pool.get('account.analytic.account')
for sla in self.browse(cr, uid, ids, context=context):
contr_ids = [x.id for x in sla.analytic_ids if x.state == 'open']
contract_obj._reapply_sla(
cr, uid, contr_ids, recalc_closed=recalc_closed,
context=context)
contract_ids = contract_obj.search(cr, uid,
[('sla_ids', 'in', ids),
('state', '=', 'open')],
context=context)

ctx = {} if context is None else context.copy()
ctx.update({'sla_ids': ids})
contract_obj._reapply_sla(cr, uid, contract_ids,
recalc_closed=recalc_closed,
context=ctx)
return True

def reapply_slas(self, cr, uid, ids, context=None):
Expand Down
104 changes: 57 additions & 47 deletions project_sla/project_sla_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
import logging
_logger = logging.getLogger(__name__)

FMT = {
'datetime': DT_FMT,
'date': D_FMT,
}

SLA_STATES = [('5', 'Failed'), ('4', 'Will Fail'), ('3', 'Warning'),
('2', 'Watching'), ('1', 'Achieved')]
Expand All @@ -56,6 +60,32 @@ def safe_getattr(obj, dotattr, default=False):
return obj


def datetime2str(dt_value, fmt):
""" Tolerant datetime to string conversion
:param datetime dt_value: datetime value to convert to string
:param str fmt: conversion format
"""
return dt_value and dt.strftime(dt_value, fmt) or None


def get_sla_date(sla, doc, field='control'):
""" Converts control or start field value to datetime object.
"""
assert field in ('control', 'start'), (
"field must be in ('control', 'start')")
if field == 'control':
sla_field = sla.control_field_id
elif field == 'start':
sla_field = sla.start_field_id

val = getattr(doc, sla_field.name)

# TODO: Is this behavior correct? I mean date of format
# %Y-%m-%d 00:00:00
return dt.strptime(val, FMT[sla_field.ttype]) if val else None


class SLAControl(orm.Model):

"""
Expand All @@ -67,16 +97,18 @@ class SLAControl(orm.Model):
_description = 'SLA Control Registry'

_columns = {
'doc_id': fields.integer('Document ID', readonly=True),
'doc_model': fields.char('Document Model', size=128, readonly=True),
'doc_id': fields.integer('Document ID', readonly=True, select=True),
'doc_model': fields.char('Document Model', size=128, readonly=True,
select=True),
'sla_line_id': fields.many2one(
'project.sla.line', 'Service Agreement'),
'sla_warn_date': fields.datetime('Warning Date'),
'sla_limit_date': fields.datetime('Limit Date'),
'sla_start_date': fields.datetime('Start Date'),
'sla_close_date': fields.datetime('Close Date'),
'project.sla.line', 'Service Agreement', select=True),
'sla_warn_date': fields.datetime('Warning Date', select=True),
'sla_limit_date': fields.datetime('Limit Date', select=True),
'sla_start_date': fields.datetime('Start Date', select=True),
'sla_close_date': fields.datetime('Close Date', select=True),
'sla_achieved': fields.integer('Achieved?'),
'sla_state': fields.selection(SLA_STATES, string="SLA Status"),
'sla_state': fields.selection(SLA_STATES, string="SLA Status",
select=True),
'locked': fields.boolean(
'Recalculation disabled',
help="Safeguard manual changes from future automatic "
Expand Down Expand Up @@ -145,7 +177,8 @@ def _compute_sla_date(self, cr, uid, working_hours, res_uid,
16:00 of the next day.
"""
assert isinstance(start_date, dt), (
"start_date must be instance of 'datetime'.")
"start_date must be instance of 'datetime'. "
"Got %s" % repr(start_date))
assert isinstance(hours, int) and hours >= 0, (
"hours must be int and >= 0. got %s" % repr(hours))

Expand Down Expand Up @@ -183,55 +216,34 @@ def _get_computed_slas(self, cr, uid, doc, context=None):
* Control date, used to calculate SLA achievement, is defined in the
SLA Definition rules.
"""
def datetime2str(dt_value, fmt): # tolerant datetime to string
return dt_value and dt.strftime(dt_value, fmt) or None

def get_sla_date(sla, doc, field='control'):
""" Converts control field value to datetime object.
"""
assert field in ('control', 'start'), (
"field must be in ('control', 'start')")
if field == 'control':
sla_field = sla.control_field_id
elif field == 'start':
sla_field = sla.start_field_id

val = getattr(doc, sla_field.name)
if val and sla_field.ttype == 'datetime':
return dt.strptime(val, DT_FMT)
elif val and sla_field.ttype == 'date':
# TODO: Is this behavior correct? I mean date of format
# %Y-%m-%d 00:00:00
return dt.strptime(val, D_FMT)
return None

res = []
res_uid = safe_getattr(doc, 'user_id.id') or uid
cal = safe_getattr(doc, 'project_id.resource_calendar_id.id')
sla_ids = (safe_getattr(doc, 'analytic_account_id.sla_ids') or
safe_getattr(doc, 'project_id.analytic_account_id.sla_ids'))
if not sla_ids:
return res

for sla in sla_ids:
res = []
for sla in (sla_ids or []):
if sla.control_model != doc._table_name:
continue # SLA not for this model; skip

start_date = get_sla_date(sla, doc, 'start')
control_date = get_sla_date(sla, doc, 'control')

if not start_date:
continue # Skip SLA that have not start_date defined

for l in sla.sla_line_ids:
eval_context = {'o': doc, 'obj': doc, 'object': doc}
if not l.condition or safe_eval(l.condition, eval_context):

start_date = get_sla_date(sla, doc, 'start')
res_uid = safe_getattr(doc, 'user_id.id') or uid
cal = safe_getattr(
doc, 'project_id.resource_calendar_id.id')
warn_date = self._compute_sla_date(
cr, uid, cal, res_uid, start_date, l.warn_qty,
context=context)
lim_date = self._compute_sla_date(
cr, uid, cal, res_uid, warn_date,
l.limit_qty - l.warn_qty,
context=context)

# evaluate sla state
control_date = get_sla_date(sla, doc, 'control')
if control_date is not None:
if control_date > lim_date:
sla_val, sla_state = 0, '5' # failed
Expand Down Expand Up @@ -286,13 +298,11 @@ def store_sla_control(self, cr, uid, docs, context=None):
if sla_recs:
slas = []
for sla_rec in sla_recs:
sla_line_id = sla_rec.get('sla_line_id')
if sla_line_id in control:
control_rec = control.get(sla_line_id)
if not control_rec.locked:
slas += m2m.write(control_rec.id, sla_rec)
else:
control_rec = control.get(sla_rec['sla_line_id'], None)
if control_rec is None:
slas += m2m.add(sla_rec)
elif not control_rec.locked:
slas += m2m.write(control_rec.id, sla_rec)
global_sla = max([sla[2].get('sla_state') for sla in slas])
else:
slas = m2m.clear()
Expand Down
36 changes: 20 additions & 16 deletions project_sla/report/report_sla.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
# -*- coding: utf-8 -*-

from openerp import tools
from openerp.osv import fields, orm

from ..project_sla_control import SLA_STATES


class report_sla(orm.Model):
class ReportSla(orm.Model):
_name = "project.sla.report"
_description = "Project SLA report"
_auto = False
_order = ('date_year, date_quarter, date_month, date_week, sla_closed, '
'sla_state')
_order = ('date')

# Overridden to automaticaly calculate correct achieved percent for any
# group result
def read_group(self, cr, uid, *args, **kwargs):
res = super(report_sla, self).read_group(cr, uid, *args, **kwargs)
res = super(ReportSla, self).read_group(cr, uid, *args, **kwargs)
for gres in res:
if 'achieved_count' in gres and 'total_count' in gres:
acount = float(gres['achieved_count'])
Expand All @@ -39,6 +40,7 @@ def _get_achieved_percent(self, cr, uid, ids, field, arg, context=None):
'date_quarter': fields.char('Quarter'),
'date_month': fields.char('Month'),
'date_week': fields.char('Week'),
'date': fields.date('Date'),
'sla_closed': fields.boolean('Is Closed'),
'total_count': fields.integer('Total Count'),
'achieved_count': fields.integer('Achieved Count'),
Expand All @@ -55,15 +57,16 @@ def init(self, cr):
sql = """
CREATE OR REPLACE VIEW %(report_name)s AS (
SELECT
psc.id AS id,
im.id AS document_model_id,
ps.name AS sla_name,
psl.name AS sla_line_name,
psc.sla_state AS sla_state,
to_char(psc.sla_start_date, 'YYYY') AS date_year,
to_char(psc.sla_start_date, 'Q') AS date_quarter,
to_char(psc.sla_start_date, 'Month') AS date_month,
to_char(psc.sla_start_date, 'WW') AS date_week,
psc.id AS id,
im.id AS document_model_id,
ps.name AS sla_name,
psl.name AS sla_line_name,
psc.sla_state AS sla_state,
to_char(psc.sla_start_date, 'YYYY') AS date_year,
to_char(psc.sla_start_date, 'YYYY-Q') AS date_quarter,
to_char(psc.sla_start_date, 'YYYY-MM') AS date_month,
to_char(psc.sla_start_date, 'YYYY-MM-WW') AS date_week,
psc.sla_start_date AS date,
-- Special fields
1 AS total_count,
Expand All @@ -82,11 +85,12 @@ def init(self, cr):
END AS sla_closed
FROM project_sla_control AS psc
LEFT JOIN project_sla_line AS psl
ON psl.id = psc.sla_line_id
ON psl.id = psc.sla_line_id
LEFT JOIN project_sla AS ps
ON ps.id = psl.sla_id
ON ps.id = psl.sla_id
LEFT JOIN ir_model AS im
ON im.model = ps.control_model
ON im.model = ps.control_model
ORDER BY psc.sla_start_date ASC
)
""" % {'report_name': report_name}
cr.execute(sql)
6 changes: 5 additions & 1 deletion project_sla/report/report_sla_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<field name="date_quarter"/>
<field name="date_month"/>
<field name="date_week"/>
<field name="date"/>
<field name="sla_closed"/>
<field name="total_count" sum="# of Lines"/>
<field name="achieved_count" sum="# of Achieved Lines"/>
Expand All @@ -33,7 +34,6 @@
<field name="arch" type="xml">
<graph string="SLA Analysis" type="bar">
<field name="sla_name"/>
<!--<field name="achieved_count"/>-->
<field name="total_count"/>
<field name="sla_state" group="True"/>
</graph>
Expand All @@ -53,6 +53,7 @@
<field name="date_quarter"/>
<field name="date_month"/>
<field name="date_week"/>
<field name="date"/>

<filter name="SLA Closed" domain="[('sla_closed','=',True)]"/>
<filter name="SLA Open" domain="[('sla_closed','=',False)]"/>
Expand Down Expand Up @@ -83,6 +84,9 @@
<filter name="date_week"
string="Week"
context="{'group_by': 'date_week'}"/>
<filter name="date"
string="Date"
context="{'group_by': 'date'}"/>
</group>
</search>
</field>
Expand Down

0 comments on commit 531255f

Please sign in to comment.