Browse Source

Merge pull request #12 from dreispt/master

Remove files from deprecated project
pull/13/head
Yannick Vaucher 10 years ago
parent
commit
a64d20257a
  1. 19
      .coveragerc
  2. 56
      .gitignore
  3. 44
      .travis.yml
  4. 2
      README.md
  5. 22
      __unported__/account_analytic_analysis_recurring/__init__.py
  6. 48
      __unported__/account_analytic_analysis_recurring/__openerp__.py
  7. 129
      __unported__/account_analytic_analysis_recurring/account_analytic_analysis_recurring.pot
  8. 209
      __unported__/account_analytic_analysis_recurring/account_analytic_analysis_recurring.py
  9. 16
      __unported__/account_analytic_analysis_recurring/account_analytic_analysis_recurring_cron.xml
  10. 44
      __unported__/account_analytic_analysis_recurring/account_analytic_analysis_recurring_view.xml
  11. 24
      __unported__/analytic_hours_block/__init__.py
  12. 59
      __unported__/analytic_hours_block/__openerp__.py
  13. 426
      __unported__/analytic_hours_block/hours_block.py
  14. 24
      __unported__/analytic_hours_block/hours_block_data.xml
  15. 25
      __unported__/analytic_hours_block/hours_block_menu.xml
  16. 172
      __unported__/analytic_hours_block/hours_block_view.xml
  17. 470
      __unported__/analytic_hours_block/i18n/analytic_hours_block.pot
  18. 38
      __unported__/analytic_hours_block/product.py
  19. 18
      __unported__/analytic_hours_block/product_view.xml
  20. 33
      __unported__/analytic_hours_block/project.py
  21. 19
      __unported__/analytic_hours_block/project_view.xml
  22. 12
      __unported__/analytic_hours_block/report.xml
  23. 21
      __unported__/analytic_hours_block/report/__init__.py
  24. 53
      __unported__/analytic_hours_block/report/hours_block.py
  25. 263
      __unported__/analytic_hours_block/report/hours_block.rml
  26. 11
      __unported__/analytic_hours_block/security/hours_block_security.xml
  27. 3
      __unported__/analytic_hours_block/security/ir.model.access.csv
  28. 5
      __unported__/project_sla/__init__.py
  29. 132
      __unported__/project_sla/__openerp__.py
  30. 69
      __unported__/project_sla/analytic_account.py
  31. 24
      __unported__/project_sla/analytic_account_view.xml
  32. 296
      __unported__/project_sla/i18n/project_sla.pot
  33. BIN
      __unported__/project_sla/images/10_sla_contract.png
  34. BIN
      __unported__/project_sla/images/20_sla_definition.png
  35. BIN
      __unported__/project_sla/images/30_sla_controlled.png
  36. 75
      __unported__/project_sla/m2m.py
  37. 29
      __unported__/project_sla/project_issue.py
  38. 60
      __unported__/project_sla/project_issue_view.xml
  39. 86
      __unported__/project_sla/project_sla.py
  40. 322
      __unported__/project_sla/project_sla_control.py
  41. 18
      __unported__/project_sla/project_sla_control_data.xml
  42. 25
      __unported__/project_sla/project_sla_control_view.xml
  43. 138
      __unported__/project_sla/project_sla_demo.xml
  44. 48
      __unported__/project_sla/project_sla_view.xml
  45. 20
      __unported__/project_sla/project_view.xml
  46. 8
      __unported__/project_sla/security/ir.model.access.csv
  47. BIN
      __unported__/project_sla/static/src/img/icon.png
  48. 66
      __unported__/project_sla/test/project_sla.yml

19
.coveragerc

@ -1,19 +0,0 @@
# Config file .coveragerc
[report]
omit =
/usr/*
*/bin/*
*/lib/*
*/odoo/*
*/openerp/*
*/tests/*
*__init__.py
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma
pragma: no cover
# Don't complain about null context checking
if context is None:

56
.gitignore

@ -1,56 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
# Pycharm
.idea
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Sphinx documentation
docs/_build/
# Backup files
*~
*.swp

44
.travis.yml

@ -1,44 +0,0 @@
# Config file .travis.yml
language: python
python:
# - "pypy" # not supported by odoo 8
# - "3.4" # not supported by odoo 8
# - "3.3" # not supported by odoo 8
- "2.7"
# - "2.6" # not supported by odoo 8
env:
- ODOO="https://github.com/savoirfairelinux/odoo/archive/setuptools-addons.tar.gz" # Temp until https://github.com/odoo/odoo/issues/185 or https://github.com/odoo/odoo/issues/441 is fixed
# - ODOO="https://github.com/odoo/odoo/archive/master.tar.gz"
# - ODOO="https://github.com/OCA/OCB/archive/master.zip"
# Need coveralls for coverage reports
# Need flake8 for pep8 testing
# Manually get PyChart
# Install tested version of odoo (official or ocb)
# Get modules from other repos which have dependencies (in this case travel requires modules from lp:partner-contact-management and lp:openerp-hr
install:
- pip install coveralls flake8
- pip install http://download.gna.org/pychart/PyChart-1.39.tar.gz
- pip install ${ODOO}
# Create databae
# Pre-install modules and dependencies
before_script:
- createdb test
# Test with flake, ignore F401 for __init__.py files, use a max length of 120
# Run tests with coverage
# Only test modules in repo (list populated by directories in repo)
# Preload modules before testing to only run tests of repo's modules
# Include current directory and dependent repos in addons-path as well as official addons
script:
- flake8 . --max-line-length=120 --exclude=__unported__ --filename=__init__.py --ignore=F401
- flake8 . --max-line-length=120 --exclude=__unported__,__init__.py
- odoo.py -d test --stop-after-init --init=$(python -c 'import os; print(",".join(x for x in os.listdir(".") if os.path.isdir(x) and not x.startswith(".") and x != "__unported__"))') --addons-path=$(pwd),`python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"`/addons
- coverage run $(which odoo.py) -d test --test-enable --log-level=test --stop-after-init --init=$(python -c 'import os; print(",".join(x for x in os.listdir(".") if os.path.isdir(x) and not x.startswith(".") and x != "__unported__"))') --addons-path=$(pwd),`python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"`/addons
after_success:
coveralls

2
README.md

@ -1,3 +1,3 @@
**IMPORTANT**
# Warning: DEPRECATED! #
These modules are now available from the [Project & Service Management](https://github.com/OCA/project-service) repository. These modules are now available from the [Project & Service Management](https://github.com/OCA/project-service) repository.

22
__unported__/account_analytic_analysis_recurring/__init__.py

@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import account_analytic_analysis_recurring

48
__unported__/account_analytic_analysis_recurring/__openerp__.py

@ -1,48 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Contracts Management recurring',
'version': '0.1',
'category': 'Other',
'description': """
This module add a new feature in contracts to manage recurring invoice
=======================================================================================
This is a backport of the new V8 feature available in trunk and saas. With the V8 release this module will be deprecated.
It also add a little feature, you can use #START# and #END# in the contract line to automatically insert the dates of the invoiced period.
Backport done By Yannick Buron.
""",
'author': 'OpenERP SA',
'website': 'http://openerp.com',
'depends': ['base', 'account_analytic_analysis'],
'data': [
'account_analytic_analysis_recurring_cron.xml',
'account_analytic_analysis_recurring_view.xml',
],
'demo': [''],
'test':[],
'installable': False,
'images': [],
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

129
__unported__/account_analytic_analysis_recurring/account_analytic_analysis_recurring.pot

@ -1,129 +0,0 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * account_analytic_analysis_recurring
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-02-21 11:41+0000\n"
"PO-Revision-Date: 2014-02-21 11:41+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_analytic_analysis_recurring
#: field:account.analytic.invoice.line,price_subtotal:0
msgid "Sub Total"
msgstr ""
#. module: account_analytic_analysis_recurring
#: field:account.analytic.account,recurring_rule_type:0
msgid "Recurrency"
msgstr ""
#. module: account_analytic_analysis_recurring
#: field:account.analytic.invoice.line,price_unit:0
msgid "Unit Price"
msgstr ""
#. module: account_analytic_analysis_recurring
#: view:account.analytic.account:0
msgid ". create invoices"
msgstr ""
#. module: account_analytic_analysis_recurring
#: view:account.analytic.account:0
msgid "Account Analytic Lines"
msgstr ""
#. module: account_analytic_analysis_recurring
#: field:account.analytic.account,recurring_invoice_line_ids:0
msgid "Invoice Lines"
msgstr ""
#. module: account_analytic_analysis_recurring
#: field:account.analytic.invoice.line,uom_id:0
msgid "Unit of Measure"
msgstr ""
#. module: account_analytic_analysis_recurring
#: selection:account.analytic.account,recurring_rule_type:0
msgid "Day(s)"
msgstr ""
#. module: account_analytic_analysis_recurring
#: help:account.analytic.account,recurring_rule_type:0
msgid "Invoice automatically repeat at specified interval"
msgstr ""
#. module: account_analytic_analysis_recurring
#: field:account.analytic.invoice.line,product_id:0
msgid "Product"
msgstr ""
#. module: account_analytic_analysis_recurring
#: field:account.analytic.invoice.line,name:0
msgid "Description"
msgstr ""
#. module: account_analytic_analysis_recurring
#: field:account.analytic.account,recurring_interval:0
msgid "Repeat Every"
msgstr ""
#. module: account_analytic_analysis_recurring
#: view:account.analytic.account:0
msgid "Recurring Invoices"
msgstr ""
#. module: account_analytic_analysis_recurring
#: field:account.analytic.account,recurring_invoices:0
msgid "Generate recurring invoices automatically"
msgstr ""
#. module: account_analytic_analysis_recurring
#: selection:account.analytic.account,recurring_rule_type:0
msgid "Year(s)"
msgstr ""
#. module: account_analytic_analysis_recurring
#: selection:account.analytic.account,recurring_rule_type:0
msgid "Week(s)"
msgstr ""
#. module: account_analytic_analysis_recurring
#: field:account.analytic.invoice.line,quantity:0
msgid "Quantity"
msgstr ""
#. module: account_analytic_analysis_recurring
#: model:ir.model,name:account_analytic_analysis_recurring.model_account_analytic_invoice_line
msgid "account.analytic.invoice.line"
msgstr ""
#. module: account_analytic_analysis_recurring
#: field:account.analytic.account,recurring_next_date:0
msgid "Date of Next Invoice"
msgstr ""
#. module: account_analytic_analysis_recurring
#: field:account.analytic.invoice.line,analytic_account_id:0
#: model:ir.model,name:account_analytic_analysis_recurring.model_account_analytic_account
msgid "Analytic Account"
msgstr ""
#. module: account_analytic_analysis_recurring
#: selection:account.analytic.account,recurring_rule_type:0
msgid "Month(s)"
msgstr ""
#. module: account_analytic_analysis_recurring
#: help:account.analytic.account,recurring_interval:0
msgid "Repeat every (Days/Week/Month/Year)"
msgstr ""

209
__unported__/account_analytic_analysis_recurring/account_analytic_analysis_recurring.py

@ -1,209 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from dateutil.relativedelta import relativedelta
import datetime
import logging
import time
from openerp.osv import osv, fields
from openerp.osv.orm import intersect, except_orm
import openerp.tools
from openerp.tools.translate import _
from openerp.addons.decimal_precision import decimal_precision as dp
_logger = logging.getLogger(__name__)
class account_analytic_invoice_line(osv.osv):
_name = "account.analytic.invoice.line"
def _amount_line(self, cr, uid, ids, prop, unknow_none, unknow_dict, context=None):
res = {}
for line in self.browse(cr, uid, ids, context=context):
res[line.id] = line.quantity * line.price_unit
if line.analytic_account_id.pricelist_id:
cur = line.analytic_account_id.pricelist_id.currency_id
res[line.id] = self.pool.get('res.currency').round(cr, uid, cur, res[line.id])
return res
_columns = {
'product_id': fields.many2one('product.product','Product',required=True),
'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account'),
'name': fields.text('Description', required=True),
'quantity': fields.float('Quantity', required=True),
'uom_id': fields.many2one('product.uom', 'Unit of Measure',required=True),
'price_unit': fields.float('Unit Price', required=True),
'price_subtotal': fields.function(_amount_line, string='Sub Total', type="float",digits_compute= dp.get_precision('Account')),
}
_defaults = {
'quantity' : 1,
}
def product_id_change(self, cr, uid, ids, product, uom_id, qty=0, name='', partner_id=False, price_unit=False, pricelist_id=False, company_id=None, context=None):
context = context or {}
uom_obj = self.pool.get('product.uom')
company_id = company_id or False
context.update({'company_id': company_id, 'force_company': company_id, 'pricelist_id': pricelist_id})
if not product:
return {'value': {'price_unit': 0.0}, 'domain':{'product_uom':[]}}
if partner_id:
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
if part.lang:
context.update({'lang': part.lang})
result = {}
res = self.pool.get('product.product').browse(cr, uid, product, context=context)
result.update({'name':res.partner_ref or False,'uom_id': uom_id or res.uom_id.id or False, 'price_unit': res.list_price or 0.0})
if res.description:
result['name'] += '\n'+res.description
res_final = {'value':result}
if result['uom_id'] != res.uom_id.id:
selected_uom = uom_obj.browse(cr, uid, result['uom_id'], context=context)
new_price = uom_obj._compute_price(cr, uid, res.uom_id.id, res_final['value']['price_unit'], result['uom_id'])
res_final['value']['price_unit'] = new_price
return res_final
class account_analytic_account(osv.osv):
_name = "account.analytic.account"
_inherit = "account.analytic.account"
_columns = {
'recurring_invoice_line_ids': fields.one2many('account.analytic.invoice.line', 'analytic_account_id', 'Invoice Lines'),
'recurring_invoices' : fields.boolean('Generate recurring invoices automatically'),
'recurring_rule_type': fields.selection([
('daily', 'Day(s)'),
('weekly', 'Week(s)'),
('monthly', 'Month(s)'),
('yearly', 'Year(s)'),
], 'Recurrency', help="Invoice automatically repeat at specified interval"),
'recurring_interval': fields.integer('Repeat Every', help="Repeat every (Days/Week/Month/Year)"),
'recurring_next_date': fields.date('Date of Next Invoice'),
}
_defaults = {
'recurring_interval': 1,
'recurring_next_date': lambda *a: time.strftime('%Y-%m-%d'),
'recurring_rule_type':'monthly'
}
def onchange_recurring_invoices(self, cr, uid, ids, recurring_invoices, date_start=False, context=None):
value = {}
if date_start and recurring_invoices:
value = {'value': {'recurring_next_date': date_start}}
return value
def _prepare_invoice(self, cr, uid, contract, context=None):
context = context or {}
inv_obj = self.pool.get('account.invoice')
journal_obj = self.pool.get('account.journal')
fpos_obj = self.pool.get('account.fiscal.position')
lang_obj = self.pool.get('res.lang')
if not contract.partner_id:
raise osv.except_osv(_('No Customer Defined!'),_("You must first select a Customer for Contract %s!") % contract.name )
fpos = contract.partner_id.property_account_position or False
journal_ids = journal_obj.search(cr, uid, [('type', '=','sale'),('company_id', '=', contract.company_id.id or False)], limit=1)
if not journal_ids:
raise osv.except_osv(_('Error!'),
_('Please define a sale journal for the company "%s".') % (contract.company_id.name or '', ))
partner_payment_term = contract.partner_id.property_payment_term and contract.partner_id.property_payment_term.id or False
inv_data = {
'reference': contract.code or False,
'account_id': contract.partner_id.property_account_receivable.id,
'type': 'out_invoice',
'partner_id': contract.partner_id.id,
'currency_id': contract.partner_id.property_product_pricelist.id or False,
'journal_id': len(journal_ids) and journal_ids[0] or False,
'date_invoice': contract.recurring_next_date,
'origin': contract.name,
'fiscal_position': fpos and fpos.id,
'payment_term': partner_payment_term,
'company_id': contract.company_id.id or False,
}
invoice_id = inv_obj.create(cr, uid, inv_data, context=context)
for line in contract.recurring_invoice_line_ids:
res = line.product_id
account_id = res.property_account_income.id
if not account_id:
account_id = res.categ_id.property_account_income_categ.id
account_id = fpos_obj.map_account(cr, uid, fpos, account_id)
taxes = res.taxes_id or False
tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
if 'old_date' in context:
lang_ids = lang_obj.search(cr, uid, [('code', '=', contract.partner_id.lang)], context=context)
format = lang_obj.browse(cr, uid, lang_ids, context=context)[0].date_format
line.name = line.name.replace('#START#', context['old_date'].strftime(format))
line.name = line.name.replace('#END#', context['next_date'].strftime(format))
invoice_line_vals = {
'name': line.name,
'account_id': account_id,
'account_analytic_id': contract.id,
'price_unit': line.price_unit or 0.0,
'quantity': line.quantity,
'uos_id': line.uom_id.id or False,
'product_id': line.product_id.id or False,
'invoice_id' : invoice_id,
'invoice_line_tax_id': [(6, 0, tax_id)],
}
self.pool.get('account.invoice.line').create(cr, uid, invoice_line_vals, context=context)
inv_obj.button_compute(cr, uid, [invoice_id], context=context)
return invoice_id
def recurring_create_invoice(self, cr, uid, automatic=False, context=None):
context = context or {}
current_date = time.strftime('%Y-%m-%d')
contract_ids = self.search(cr, uid, [('recurring_next_date','<=', current_date), ('state','=', 'open'), ('recurring_invoices','=', True)])
for contract in self.browse(cr, uid, contract_ids, context=context):
next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d")
interval = contract.recurring_interval
if contract.recurring_rule_type == 'daily':
old_date = next_date-relativedelta(days=+interval)
new_date = next_date+relativedelta(days=+interval)
elif contract.recurring_rule_type == 'weekly':
old_date = next_date-relativedelta(weeks=+interval)
new_date = next_date+relativedelta(weeks=+interval)
else:
old_date = next_date+relativedelta(months=+interval)
new_date = next_date+relativedelta(months=+interval)
context['old_date'] = old_date
context['next_date'] = datetime.datetime.strptime(contract.recurring_next_date or current_date,"%Y-%m-%d")
invoice_id = self._prepare_invoice(cr, uid, contract, context=context)
self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context)
return True

16
__unported__/account_analytic_analysis_recurring/account_analytic_analysis_recurring_cron.xml

@ -1,16 +0,0 @@
<?xml version="1.0" encoding='UTF-8'?>
<openerp>
<data>
<record model="ir.cron" id="account_analytic_cron_for_invoice">
<field name="name">Generate Recurring Invoices from Contracts</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="model" eval="'account.analytic.account'"/>
<field name="function" eval="'recurring_create_invoice'"/>
<field name="args" eval="'()'"/>
</record>
</data>
</openerp>

44
__unported__/account_analytic_analysis_recurring/account_analytic_analysis_recurring_view.xml

@ -1,44 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="account_analytic_account_recurring_form_form" model="ir.ui.view">
<field name="name">account.analytic.account.invoice.recurring.form.inherit</field>
<field name="model">account.analytic.account</field>
<field name="inherit_id" ref="account_analytic_analysis.account_analytic_account_form_form"/>
<field eval="40" name="priority"/>
<field name="arch" type="xml">
<group name='invoice_on_timesheets' position='after'>
<separator string="Recurring Invoices" attrs="{'invisible': [('recurring_invoices','!=',True)]}"/>
<div>
<field name="recurring_invoices" on_change="onchange_recurring_invoices(recurring_invoices, date_start)" class="oe_inline"/>
<label for="recurring_invoices" />
<button class="oe_link" name="recurring_create_invoice" attrs="{'invisible': [('recurring_invoices','!=',True)]}" string=". create invoices" type="object" groups="base.group_no_one"/>
</div>
<group attrs="{'invisible': [('recurring_invoices','!=',True)]}">
<label for="recurring_interval"/>
<div>
<field name="recurring_interval" class="oe_inline" attrs="{'required': [('recurring_invoices', '=', True)]}"/>
<field name="recurring_rule_type" class="oe_inline" attrs="{'required': [('recurring_invoices', '=', True)]}"/>
</div>
<field name="recurring_next_date"/>
</group>
<label for="recurring_invoice_line_ids" attrs="{'invisible': [('recurring_invoices','=',False)]}"/>
<div attrs="{'invisible': [('recurring_invoices','=',False)]}">
<field name="recurring_invoice_line_ids">
<tree string="Account Analytic Lines" editable="bottom">
<field name="product_id" on_change="product_id_change(product_id, uom_id, quantity, name, parent.partner_id, price_unit, parent.pricelist_id, parent.company_id)"/>
<field name="name"/>
<field name="quantity"/>
<field name="uom_id"/>
<field name="price_unit"/>
<field name="price_subtotal"/>
</tree>
</field>
</div>
</group>
</field>
</record>
</data>
</openerp>

24
__unported__/analytic_hours_block/__init__.py

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Vincent Renaville, ported by Joel Grand-Guillaume
# Copyright 2010-2012 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import hours_block
import report
import product
import project

59
__unported__/analytic_hours_block/__openerp__.py

@ -1,59 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Vincent Renaville, ported by Joel Grand-Guillaume
# Copyright 2010-2012 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
"name": "Project Hours Blocks Management",
"version": "1.5",
"category": "Generic Modules/Projects & Services",
"description": """
Project Hours Blocks Management
===============================
This module allows you to handle hours blocks,
to follow for example the user support contracts.
This means, you sell a product of type "hours block"
then you input the spent hours on the hours block and
you can track and follow how much has been used.
""",
"author": "Camptocamp",
"license": 'AGPL-3',
"website": "http://www.camptocamp.com",
"depends": [
"account",
"hr_timesheet_invoice",
"analytic",
"project",
],
"data": [
"report.xml",
"hours_block_view.xml",
"hours_block_data.xml",
"hours_block_menu.xml",
"product_view.xml",
"project_view.xml",
"report.xml",
"security/hours_block_security.xml",
"security/ir.model.access.csv",
],
"active": False,
"installable": False
}

426
__unported__/analytic_hours_block/hours_block.py

@ -1,426 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Vincent Renaville, ported by Joel Grand-Guillaume
# Copyright 2010-2012 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
class AccountHoursBlock(orm.Model):
_name = "account.hours.block"
_inherit = ['mail.thread']
def _get_last_action(self, cr, uid, ids, name, arg, context=None):
""" Return the last analytic line date for an invoice"""
res = {}
for block in self.browse(cr, uid, ids, context=context):
cr.execute("SELECT max(al.date) FROM account_analytic_line AS al"
" WHERE al.invoice_id = %s", (block.invoice_id.id,))
fetch_res = cr.fetchone()
res[block.id] = fetch_res[0] if fetch_res else False
return res
def _compute_hours(self, cr, uid, ids, fields, args, context=None):
"""Return a dict of [id][fields]"""
if isinstance(ids, (int, long)):
ids = [ids]
result = {}
aal_obj = self.pool.get('account.analytic.line')
for block in self.browse(cr, uid, ids, context=context):
result[block.id] = {'amount_hours_block': 0.0,
'amount_hours_block_done': 0.0}
# Compute hours bought
for line in block.invoice_id.invoice_line:
hours_bought = 0.0
if line.product_id and line.product_id.is_in_hours_block:
# We will now calculate the product_quantity
factor = line.uos_id.factor
if factor == 0.0:
factor = 1.0
amount = line.quantity
hours_bought += (amount / factor)
result[block.id]['amount_hours_block'] += hours_bought
# Compute hours spent
hours_used = 0.0
# Get ids of analytic line generated from
# timesheet associated to the current block
cr.execute("SELECT al.id "
"FROM account_analytic_line AS al, "
" account_analytic_journal AS aj "
"WHERE aj.id = al.journal_id "
"AND aj.type = 'general' "
"AND al.invoice_id = %s", (block.invoice_id.id,))
res_line_ids = cr.fetchall()
line_ids = [l[0] for l in res_line_ids] if res_line_ids else []
for line in aal_obj.browse(cr, uid, line_ids, context=context):
factor = 1.0
if line.product_uom_id and line.product_uom_id.factor != 0.0:
factor = line.product_uom_id.factor
factor_invoicing = 1.0
if line.to_invoice and line.to_invoice.factor != 0.0:
factor_invoicing = 1.0 - line.to_invoice.factor / 100
hours_used += ((line.unit_amount / factor) * factor_invoicing)
result[block.id]['amount_hours_block_done'] = hours_used
return result
def _compute_amount(self, cr, uid, ids, fields, args, context=None):
if context is None:
context = {}
result = {}
aal_obj = self.pool.get('account.analytic.line')
pricelist_obj = self.pool.get('product.pricelist')
for block in self.browse(cr, uid, ids, context=context):
result[block.id] = {'amount_hours_block': 0.0,
'amount_hours_block_done': 0.0}
# Compute amount bought
for line in block.invoice_id.invoice_line:
amount_bought = 0.0
if line.product_id:
## We will now calculate the product_quantity
factor = line.uos_id.factor
if factor == 0.0:
factor = 1.0
amount = line.quantity * line.price_unit
amount_bought += (amount / factor)
result[block.id]['amount_hours_block'] += amount_bought
# Compute total amount
# Get ids of analytic line generated from timesheet associated to current block
cr.execute("SELECT al.id FROM account_analytic_line AS al,"
" account_analytic_journal AS aj"
" WHERE aj.id = al.journal_id"
" AND aj.type='general'"
" AND al.invoice_id = %s", (block.invoice_id.id,))
res_line_ids = cr.fetchall()
line_ids = [l[0] for l in res_line_ids] if res_line_ids else []
total_amount = 0.0
for line in aal_obj.browse(cr, uid, line_ids, context=context):
factor_invoicing = 1.0
if line.to_invoice and line.to_invoice.factor != 0.0:
factor_invoicing = 1.0 - line.to_invoice.factor / 100
ctx = dict(context, uom=line.product_uom_id.id)
amount = pricelist_obj.price_get(
cr, uid,
[line.account_id.pricelist_id.id],
line.product_id.id,
line.unit_amount or 1.0,
line.account_id.partner_id.id or False,
ctx)[line.account_id.pricelist_id.id]
total_amount += amount * line.unit_amount * factor_invoicing
result[block.id]['amount_hours_block_done'] += total_amount
return result
def _compute(self, cr, uid, ids, fields, args, context=None):
result = {}
block_per_types = {}
for block in self.browse(cr, uid, ids, context=context):
block_per_types.setdefault(block.type, []).append(block.id)
for block_type in block_per_types:
if block_type:
func = getattr(self, "_compute_%s" % block_type)
result.update(func(cr, uid, ids, fields, args, context=context))
for block in result:
result[block]['amount_hours_block_delta'] = \
result[block]['amount_hours_block'] - \
result[block]['amount_hours_block_done']
return result
def _get_analytic_line(self, cr, uid, ids, context=None):
invoice_ids = []
an_lines_obj = self.pool.get('account.analytic.line')
block_obj = self.pool.get('account.hours.block')
for line in an_lines_obj.browse(cr, uid, ids, context=context):
if line.invoice_id:
invoice_ids.append(line.invoice_id.id)
return block_obj.search(
cr, uid, [('invoice_id', 'in', invoice_ids)], context=context)
def _get_invoice(self, cr, uid, ids, context=None):
block_ids = set()
inv_obj = self.pool.get('account.invoice')
for invoice in inv_obj.browse(cr, uid, ids, context=context):
block_ids.update([inv.id for inv in invoice.account_hours_block_ids])
return list(block_ids)
def action_send_block(self, cr, uid, ids, context=None):
"""Open a form to send by email. Return an action dict."""
assert len(ids) == 1, '''\
This option should only be used for a single ID at a time.'''
ir_model_data = self.pool.get('ir.model.data')
try:
template_id = ir_model_data.get_object_reference(
cr, uid, 'analytic_hours_block', 'email_template_hours_block'
)[1]
except ValueError:
template_id = False
try:
compose_form_id = ir_model_data.get_object_reference(
cr, uid, 'mail', 'email_compose_message_wizard_form'
)[1]
except ValueError:
compose_form_id = False
ctx = {
'default_model': self._name,
'default_res_id': ids[0],
'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
}
return {
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
'views': [(compose_form_id, 'form')],
'view_id': compose_form_id,
'target': 'new',
'context': ctx,
}
_recompute_triggers = {
'account.hours.block': (lambda self, cr, uid, ids, c=None:
ids, ['invoice_id', 'type'], 10),
'account.invoice': (_get_invoice, ['analytic_line_ids'], 10),
'account.analytic.line': (
_get_analytic_line,
['product_uom_id', 'unit_amount', 'to_invoice', 'invoice_id'],
10),
}
_columns = {
'amount_hours_block': fields.function(
_compute,
type='float',
string='Quantity / Amount bought',
store=_recompute_triggers,
multi='amount_hours_block_delta',
help="Amount bought by the customer. "
"This amount is expressed in the base Unit of Measure "
"(factor=1.0)"),
'amount_hours_block_done': fields.function(
_compute,
type='float',
string='Quantity / Amount used',
store=_recompute_triggers,
multi='amount_hours_block_delta',
help="Amount done by the staff. "
"This amount is expressed in the base Unit of Measure "
"(factor=1.0)"),
'amount_hours_block_delta': fields.function(
_compute,
type='float',
string='Difference',
store=_recompute_triggers,
multi='amount_hours_block_delta',
help="Difference between bought and used. "
"This amount is expressed in the base Unit of Measure "
"(factor=1.0)"),
'last_action_date': fields.function(
_get_last_action,
type='date',
string='Last action date',
help="Date of the last analytic line linked to the invoice "
"related to this block hours."),
'close_date': fields.date('Closed Date'),
'invoice_id': fields.many2one(
'account.invoice',
'Invoice',
ondelete='cascade',
required=True),
'type': fields.selection(
[('hours', 'Hours'),
('amount', 'Amount')],
string='Type of Block',
required=True,
help="The block is based on the quantity of hours "
"or on the amount."),
# Invoices related infos
'date_invoice': fields.related(
'invoice_id', 'date_invoice',
type="date",
string="Invoice Date",
store={
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids,
['invoice_id'], 10),
'account.invoice': (_get_invoice, ['date_invoice'], 10),
},
readonly=True),
'user_id': fields.related(
'invoice_id', 'user_id',
type="many2one",
relation="res.users",
string="Salesman",
store={
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids,
['invoice_id'], 10),
'account.invoice': (_get_invoice, ['user_id'], 10),
},
readonly=True),
'partner_id': fields.related(
'invoice_id', 'partner_id',
type="many2one",
relation="res.partner",
string="Partner",
store={
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids,
['invoice_id'], 10),
'account.invoice': (_get_invoice, ['partner_id'], 10),
},
readonly=True),
'name': fields.related(
'invoice_id', 'name',
type="char",
string="Description",
store={
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids,
['invoice_id'], 10),
'account.invoice': (_get_invoice, ['name'], 10),
},
readonly=True),
'number': fields.related(
'invoice_id', 'number',
type="char",
string="Number",
store={
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids,
['invoice_id'], 10),
'account.invoice': (_get_invoice, ['number'], 10),
},
readonly=True),
'journal_id': fields.related(
'invoice_id', 'journal_id',
type="many2one",
relation="account.journal",
string="Journal",
store={
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids,
['invoice_id'], 10),
'account.invoice': (_get_invoice, ['journal_id'], 10),
},
readonly=True),
'period_id': fields.related(
'invoice_id', 'period_id',
type="many2one",
relation="account.period",
string="Period",
store={
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids,
['invoice_id'], 10),
'account.invoice': (_get_invoice, ['period_id'], 10),
},
readonly=True),
'company_id': fields.related(
'invoice_id', 'company_id',
type="many2one",
relation="res.company",
string="Company",
store={
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids,
['invoice_id'], 10),
'account.invoice': (_get_invoice, ['company_id'], 10),
},
readonly=True),
'currency_id': fields.related(
'invoice_id', 'currency_id',
type="many2one",
relation="res.currency",
string="Currency",
store={
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids,
['invoice_id'], 10),
'account.invoice': (_get_invoice, ['currency_id'], 10),
},
readonly=True),
'residual': fields.related(
'invoice_id', 'residual',
type="float",
string="Residual",
store={
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids,
['invoice_id'], 10),
'account.invoice': (_get_invoice, ['residual'], 10),
},
readonly=True),
'amount_total': fields.related(
'invoice_id', 'amount_total',
type="float",
string="Total",
store={
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids,
['invoice_id'], 10),
'account.invoice': (_get_invoice, ['amount_total'], 10),
},
readonly=True),
'department_id': fields.related(
'invoice_id', 'department_id',
type='many2one',
relation='hr.department',
string='Department',
store={
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids,
['invoice_id'], 10),
'account.invoice': (_get_invoice, ['department_id'], 10),
},
readonly=True),
'state': fields.related(
'invoice_id', 'state',
type='selection',
selection=[
('draft', 'Draft'),
('proforma', 'Pro-forma'),
('proforma2', 'Pro-forma'),
('open', 'Open'),
('paid', 'Paid'),
('cancel', 'Cancelled'),
],
string='State',
readonly=True,
store={
'account.hours.block': (lambda self, cr, uid, ids, c=None: ids,
['invoice_id'], 10),
'account.invoice': (_get_invoice, ['state'], 10),
}),
}
############################################################################
## Add hours blocks on invoice
############################################################################
class AccountInvoice(orm.Model):
_inherit = 'account.invoice'
_columns = {
'account_hours_block_ids': fields.one2many(
'account.hours.block',
'invoice_id',
string='Hours Block')
}

24
__unported__/analytic_hours_block/hours_block_data.xml

@ -1,24 +0,0 @@
<?xml version="1.0" ?>
<openerp>
<!-- Mail template are declared in a NOUPDATE block
so users can freely customize/delete them -->
<data noupdate="1">
<record id="email_template_hours_block" model="email.template">
<field name="name">Hours Block - Send by Email</field>
<field name="email_from">${(object.user_id.email or object.company_id.email or 'noreply@localhost')|safe}</field>
<field name="subject">${object.company_id.name} Hours Block (Ref ${object.number or 'n/a'})</field>
<field name="email_recipients">${object.partner_id.id}</field>
<field name="model_id" ref="analytic_hours_block.model_account_hours_block"/>
<field name="auto_delete" eval="True"/>
<field name="report_template" ref="block_hours_report"/>
<field name="report_name">Hours_Block_${(object.number or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}</field>
<field name="lang">${object.partner_id.lang}</field>
<field name="body_html"><![CDATA[
<p>Hello ${object.partner_id.name},</p>
<p>Please find attached your Hours Block Report.</p>
<p>Best regards.</p>
]]></field>
</record>
</data>
</openerp>

25
__unported__/analytic_hours_block/hours_block_menu.xml

@ -1,25 +0,0 @@
<?xml version="1.0" ?>
<openerp>
<data>
<!--
Hours block menu
-->
<record model="ir.actions.act_window" id="action_all_block_hour">
<field name="context">{'search_default_running': 1, 'search_default_group_department_id': 1}</field>
<field name="name">Hours Blocks</field>
<field name="res_model">account.hours.block</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field eval="False" name="view_id"/>
</record>
<menuitem
name="Hours Block"
parent="account.menu_finance_receivables"
id="action_all_block_hour_account"
action="action_all_block_hour" />
</data>
</openerp>

172
__unported__/analytic_hours_block/hours_block_view.xml

@ -1,172 +0,0 @@
<?xml version="1.0" ?>
<openerp>
<data>
<!--
Hours block search form
-->
<record id="view_account_invoice_filter" model="ir.ui.view">
<field name="name">account.hours.block.select</field>
<field name="model">account.hours.block</field>
<field name="arch" type="xml">
<search string="Search Invoice">
<group col="7" colspan="4">
<filter name="draft" icon="terp-document-new" string="Draft" domain="[('state','=','draft')]" help="Draft Hours Blocks"/>
<filter name="running" icon="terp-dolar" string="Running" domain="[('close_date','=',False),('state','&lt;&gt;','draft')]" help="All Running Hours Block"/>
<separator orientation="vertical"/>
<filter name="overdue" icon="terp-dolar_ok!" string="Overdue" domain="[('amount_hours_block_delta','&lt;','0.0')]" help="Overdue Hours Block"/>
<separator orientation="vertical"/>
<field name="number"/>
<field name="partner_id"/>
<field name="department_id" string="Department"/>
<field name="user_id" select="1" widget="selection" string="Salesman">
<filter domain="[('user_id','=',uid)]" help="My invoices" icon="terp-personal" separator="1"/>
</field>
<field name="company_id" widget="selection"/>
</group>
<newline/>
<group expand="0" string="Group By...">
<filter string="Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Responsible" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Department" icon="terp-personal" domain="[]" context="{'group_by':'department_id'}"/>
<filter string="Invoice State" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
</group>
</search>
</field>
</record>
<!--
Hours Block View
-->
<record id="hours_block_invoice_form" model="ir.ui.view">
<field name="name">account.hours.block.form</field>
<field name="model">account.hours.block</field>
<field name="arch" type="xml">
<form string="Hours Blocks" version="7.0">
<header>
<button name="action_send_block" type="object" string="Send by Email" class="oe_highlight"/>
</header>
<sheet>
<h1>
<field name="invoice_id" placeholder="Choose an invoice..."/>
<label for="type" string="Based on:" class="oe_inline"/>
<field name="type" class="oe_inline"/>
</h1>
<group>
<field name="last_action_date" />
<field name="close_date" />
</group>
<group>
<separator colspan="4" string="Hours Quantity / Amount"/>
<field name="amount_hours_block" string="Bought"/>
<field name="amount_hours_block_done" string="Used"/>
<field name="amount_hours_block_delta" string="Difference"/>
</group>
<group>
<separator colspan="4" string="Invoice's related information"/>
<field name="date_invoice"/>
<field name="name"/>
<field name="number"/>
<field name="partner_id" groups="base.group_user"/>
<field name="user_id"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
<field name="department_id" widget="selection"/>
<field name="journal_id" invisible="1"/>
<field name="period_id" invisible="1" groups="account.group_account_user"/>
<field name="currency_id"/>
<newline/>
<field name="residual" sum="Residual Amount"/>
<field name="amount_total" sum="Total Amount"/>
<field name="state"/>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record model="ir.ui.view" id="invoice_tree_hour_block">
<field name="name">account.hours.block.tree</field>
<field name="model">account.hours.block</field>
<field name="arch" type="xml">
<tree colors="blue:state in ('draft');black:state in ('proforma','proforma2','open');gray:state in ('cancel')" string="Invoice">
<field name="date_invoice"/>
<field name="partner_id" groups="base.group_user"/>
<field name="name"/>
<field name="amount_hours_block" sum="Quantity of hours bought"/>
<field name="amount_hours_block_done" sum="Quantity of hours used" />
<field name="amount_hours_block_delta" sum="Quantity of hours difference"/>
<field name="last_action_date" />
<field name="close_date" />
<field name="journal_id" invisible="1"/>
<field name="period_id" invisible="1" groups="account.group_account_user"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
<field name="department_id" widget="selection"/>
<field name="user_id"/>
<field name="currency_id"/>
<field name="residual" sum="Residual Amount"/>
<field name="amount_total" sum="Total Amount"/>
<field name="state"/>
</tree>
</field>
</record>
<!--
Add related act_window from partner and Hours Block
-->
<act_window name="All blocks hours"
domain="[('partner_id', '=', active_id)]"
res_model="account.hours.block"
src_model="res.partner"
id="act_block_hour_from_partner"/>
<!--
Link to invoice on hours block view
-->
<act_window
domain="[('account_hours_block_ids', '=', active_id)]"
id="act_invoice_from_hours_block"
name="Invoice"
res_model="account.invoice"
src_model="account.hours.block"
view_mode="tree,form"
view_type="form"/>
<!--
Link to analytic lines on hours block view
-->
<act_window
domain="[('invoice_id.account_hours_block_ids', '=', active_id)]"
id="act_analytic_lines_from_hours_block"
name="Analytic Lines"
res_model="account.analytic.line"
src_model="account.hours.block"
view_mode="tree,form"
view_type="form"/>
<!--
Link to hours block on invoice view
-->
<act_window
domain="[('invoice_id', '=', active_id)]"
id="act_hours_block_from_invoice"
name="Hours Block"
res_model="account.hours.block"
src_model="account.invoice"
view_mode="tree,form,calendar,graph"
view_type="form"/>
</data>
</openerp>

470
__unported__/analytic_hours_block/i18n/analytic_hours_block.pot

@ -1,470 +0,0 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * analytic_hours_block
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-01-08 12:49+0000\n"
"PO-Revision-Date: 2014-01-08 12:49+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Maintenance And Support Summary"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Invoice Date:"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Group By..."
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Bought"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,close_date:0
msgid "Closed Date"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,message_unread:0
msgid "Unread Messages"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,company_id:0
msgid "Company"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,date_invoice:0
msgid "Invoice Date"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,residual:0
msgid "Residual"
msgstr ""
#. module: analytic_hours_block
#: help:account.hours.block,amount_hours_block:0
msgid "Amount bought by the customer. This amount is expressed in the base Unit of Measure (factor=1.0)"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Based on:"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,message_ids:0
msgid "Messages"
msgstr ""
#. module: analytic_hours_block
#: selection:account.hours.block,type:0
msgid "Amount"
msgstr ""
#. module: analytic_hours_block
#: selection:account.hours.block,state:0
msgid "Cancelled"
msgstr ""
#. module: analytic_hours_block
#: help:account.hours.block,message_unread:0
msgid "If checked new messages require your attention."
msgstr ""
#. module: analytic_hours_block
#: model:email.template,body_html:analytic_hours_block.email_template_hours_block
msgid "\n"
" Here is your Hours Block Report\n"
" "
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Hours Quantity / Amount"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Remaining hours:"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Quantity of hours bought:"
msgstr ""
#. module: analytic_hours_block
#: help:account.hours.block,message_summary:0
msgid "Holds the Chatter summary (number of messages, ...). This summary is directly in html format in order to be inserted in kanban views."
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Quantity of hours bought"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
#: field:account.hours.block,partner_id:0
msgid "Partner"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Quantity of hours difference"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,period_id:0
msgid "Period"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,state:0
msgid "State"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,message_follower_ids:0
msgid "Followers"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Send by Email"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "All Running Hours Block"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,last_action_date:0
msgid "Last action date"
msgstr ""
#. module: analytic_hours_block
#: selection:account.hours.block,type:0
msgid "Hours"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Description:"
msgstr ""
#. module: analytic_hours_block
#: help:account.hours.block,type:0
msgid "The block is based on the quantity of hours or on the amount."
msgstr ""
#. module: analytic_hours_block
#: model:email.template,report_name:analytic_hours_block.email_template_hours_block
msgid "Hours_Block_${(object.number or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''}"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Remaining amount:"
msgstr ""
#. module: analytic_hours_block
#: model:ir.model,name:analytic_hours_block.model_account_hours_block
msgid "account.hours.block"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,amount_hours_block:0
msgid "Quantity / Amount bought"
msgstr ""
#. module: analytic_hours_block
#: model:ir.actions.report.xml,name:analytic_hours_block.block_hours_report
msgid "Block Hours State"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Choose an invoice..."
msgstr ""
#. module: analytic_hours_block
#: selection:account.hours.block,state:0
msgid "Open"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "My invoices"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Draft Hours Blocks"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,currency_id:0
msgid "Currency"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
#: field:account.hours.block,user_id:0
msgid "Salesman"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Quantity of hours used"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
#: selection:account.hours.block,state:0
msgid "Draft"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
#: model:ir.actions.act_window,name:analytic_hours_block.action_all_block_hour
msgid "Hours Blocks"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,type:0
msgid "Type of Block"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Used"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Total Amount"
msgstr ""
#. module: analytic_hours_block
#: selection:account.hours.block,state:0
msgid "Paid"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Page"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,message_is_follower:0
msgid "Is a Follower"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Date"
msgstr ""
#. module: analytic_hours_block
#: field:account.invoice,account_hours_block_ids:0
#: model:ir.actions.act_window,name:analytic_hours_block.act_hours_block_from_invoice
#: model:ir.ui.menu,name:analytic_hours_block.action_all_block_hour_account
msgid "Hours Block"
msgstr ""
#. module: analytic_hours_block
#: help:account.hours.block,last_action_date:0
msgid "Date of the last analytic line linked to the invoice related to this block hours."
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Report Date:"
msgstr ""
#. module: analytic_hours_block
#: model:email.template,subject:analytic_hours_block.email_template_hours_block
msgid "${object.company_id.name} Hours Block (Ref ${object.number or 'n/a'})"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Invoice's related information"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Search Invoice"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Quantity"
msgstr ""
#. module: analytic_hours_block
#: help:account.hours.block,amount_hours_block_delta:0
msgid "Difference between bought and used. This amount is expressed in the base Unit of Measure (factor=1.0)"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "0.6cm 27.9cm 20.3cm 27.9cm"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Residual Amount"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Overdue Hours Block"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Amount used:"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,number:0
msgid "Number"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
#: field:account.hours.block,invoice_id:0
#: model:ir.actions.act_window,name:analytic_hours_block.act_invoice_from_hours_block
#: model:ir.model,name:analytic_hours_block.model_account_invoice
msgid "Invoice"
msgstr ""
#. module: analytic_hours_block
#: selection:account.hours.block,state:0
msgid "Pro-forma"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Responsible"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
#: field:account.hours.block,name:0
msgid "Description"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Amount bought:"
msgstr ""
#. module: analytic_hours_block
#: help:account.hours.block,amount_hours_block_done:0
msgid "Amount done by the staff. This amount is expressed in the base Unit of Measure (factor=1.0)"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Quantity of hours used:"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Invoicing"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,amount_hours_block_done:0
msgid "Quantity / Amount used"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,journal_id:0
msgid "Journal"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Running"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
#: field:account.hours.block,amount_hours_block_delta:0
msgid "Difference"
msgstr ""
#. module: analytic_hours_block
#: model:ir.actions.act_window,name:analytic_hours_block.act_block_hour_from_partner
msgid "All blocks hours"
msgstr ""
#. module: analytic_hours_block
#: report:account.hours.block:0
msgid "Deduced"
msgstr ""
#. module: analytic_hours_block
#: model:ir.actions.act_window,name:analytic_hours_block.act_analytic_lines_from_hours_block
msgid "Analytic Lines"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Invoice State"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,message_summary:0
msgid "Summary"
msgstr ""
#. module: analytic_hours_block
#: help:account.hours.block,message_ids:0
msgid "Messages and communication history"
msgstr ""
#. module: analytic_hours_block
#: view:account.hours.block:0
msgid "Overdue"
msgstr ""
#. module: analytic_hours_block
#: field:account.hours.block,amount_total:0
msgid "Total"
msgstr ""
#. module: analytic_hours_block
#: field:product.product,is_in_hours_block:0
msgid "Accounted for hours block?"
msgstr ""
#. module: analytic_hours_block
#: help:product.product,is_in_hours_block:0
msgid "Specify if you want to have invoice lines containing this product to be considered for hours blocks."
msgstr ""

38
__unported__/analytic_hours_block/product.py

@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Matthieu Dietrich
# Copyright 2014 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
class Product(orm.Model):
_name = "product.product"
_inherit = 'product.product'
_columns = {
'is_in_hours_block': fields.boolean(
'Accounted for hours block?',
help="Specify if you want to have invoice lines "
"containing this product to be considered for hours blocks.")
}
_defaults = {
'is_in_hours_block': False
}

18
__unported__/analytic_hours_block/product_view.xml

@ -1,18 +0,0 @@
<?xml version="1.0" ?>
<openerp>
<data>
<record id="view_product_hours_block_form" model="ir.ui.view">
<field name="name">product.product.block.form</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="arch" type="xml">
<div name="options" position="inside">
<field name="is_in_hours_block"/>
<label for="is_in_hours_block"/>
</div>
</field>
</record>
</data>
</openerp>

33
__unported__/analytic_hours_block/project.py

@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
from osv import orm
from openerp.tools.translate import _
class project_project(orm.Model):
_inherit = 'project.project'
def hours_block_tree_view(self, cr, uid, ids, context):
invoice_line_obj = self.pool.get('account.invoice.line')
hours_block_obj = self.pool.get('account.hours.block')
project = self.browse(cr, uid , ids)[0]
invoice_line_ids = invoice_line_obj.search(cr, uid, [('account_analytic_id', '=', project.analytic_account_id.id)])
invoice_lines = invoice_line_obj.browse(cr, uid, invoice_line_ids)
invoice_ids = [x.invoice_id.id for x in invoice_lines]
res_ids = hours_block_obj.search(cr, uid, [('invoice_id','in',invoice_ids)])
domain=False
if res_ids:
domain = [('id', 'in', res_ids)]
else:
raise orm.except_orm(_('Warning'), _("No Hours Block for this project"))
return {
'name': _('Hours Blocks'),
'domain': domain,
'res_model': 'account.hours.block',
'type': 'ir.actions.act_window',
'view_id': False,
'view_mode': 'tree,form',
'view_type': 'form',
'limit': 80,
'res_id' : res_ids or False,
}

19
__unported__/analytic_hours_block/project_view.xml

@ -1,19 +0,0 @@
<?xml version="1.0" ?>
<openerp>
<data>
<record model="ir.ui.view" id="edit_project_hours_block_link">
<field name="name">project.project.form.hours.block.link</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.edit_project"/>
<field name="type">form</field>
<field name="arch" type="xml">
<xpath expr="//button[@name='attachment_tree_view']" position="after">
<button name="hours_block_tree_view" string="Hours Block" type="object"/>
</xpath>
</field>
</record>
</data>
</openerp>

12
__unported__/analytic_hours_block/report.xml

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<report id="block_hours_report"
string="Block Hours State"
model="account.hours.block"
name="account.hours.block"
rml="analytic_hours_block/report/hours_block.rml"/>
</data>
</openerp>

21
__unported__/analytic_hours_block/report/__init__.py

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Vincent Renaville, ported by Joel Grand-Guillaume
# Copyright 2010-2012 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import hours_block

53
__unported__/analytic_hours_block/report/hours_block.py

@ -1,53 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Vincent Renaville, ported by Joel Grand-Guillaume
# Copyright 2010-2013 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import time
from openerp.report import report_sxw
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
class account_hours_block(report_sxw.rml_parse):
def __init__(self, cr, uid, name, context=None):
super(account_hours_block, self).__init__(cr, uid, name, context=context)
self.localcontext.update({'time': time,
'date_format': DEFAULT_SERVER_DATE_FORMAT,
'analytic_lines': self._get_analytic_lines,
})
self.context = context
def _get_analytic_lines(self, hours_block):
al_pool = self.pool.get('account.analytic.line')
aj_pool = self.pool.get('account.analytic.journal')
tcj_ids = aj_pool.search(self.cr, self.uid,
[('type', '=', 'general')])
al_ids = al_pool.search(self.cr,
self.uid,
[('invoice_id', '=', hours_block.invoice_id.id),
('journal_id', 'in', tcj_ids),
],
order='date desc',
context=self.context)
return al_pool.browse(self.cr, self.uid, al_ids, context=self.context)
report_sxw.report_sxw('report.account.hours.block',
'account.hours.block',
'addons/analytic_hours_block/report/hours_block.rml',
parser=account_hours_block)

263
__unported__/analytic_hours_block/report/hours_block.rml

@ -1,263 +0,0 @@
<?xml version="1.0"?>
<document filename="test.pdf">
<template pageSize="(595.0,842.0)" title="Test" author="Martin Simon" allowSplitting="20">
<pageTemplate id="first">
<frame id="first" x1="35.0" y1="35.0" width="525" height="772"/>
<pageGraphics>
<setFont name="Helvetica-Bold" size="9"/>
<drawString x="1.0cm" y="28.1cm">[[ company.name ]]</drawString>
<drawString x="17.7cm" y="28.1cm">Maintenance And Support Summary</drawString>
<setFont name="Helvetica" size="9"/>
<drawString x="1.0cm" y="2cm"> [[ formatLang(time.strftime(date_format), date=True) ]]</drawString>
<drawString x="17.7cm" y="2cm">Page <pageNumber/></drawString>
<lineMode width="0.7"/>
<lines>0.6cm 27.9cm 20.3cm 27.9cm</lines>
<setFont name="Helvetica" size="8"/>
</pageGraphics>
</pageTemplate>
</template>
<stylesheet>
<blockTableStyle id="Standard_Outline">
<blockAlignment value="LEFT"/>
<blockValign value="TOP"/>
</blockTableStyle>
<blockTableStyle id="Table1">
<lineStyle kind="LINEBELOW" colorName="#ffffff" start="0,0" stop="-1,-1"/>
<blockAlignment value="LEFT"/>
<blockValign value="TOP"/>
<blockBackground colorName="#e6e6e6" start="0,0" stop="0,-1"/>
<blockBackground colorName="#e6e6e6" start="1,0" stop="1,-1"/>
<blockBackground colorName="#e6e6e6" start="2,0" stop="2,-1"/>
<blockBackground colorName="#e6e6e6" start="0,1" stop="0,-1"/>
<blockBackground colorName="#e6e6e6" start="1,1" stop="1,-1"/>
<blockBackground colorName="#e6e6e6" start="2,1" stop="2,-1"/>
</blockTableStyle>
<blockTableStyle id="Table6">
<blockAlignment value="LEFT"/>
<blockValign value="TOP"/>
<lineStyle kind="LINEBEFORE" colorName="#000000" start="0,0" stop="0,-1"/>
<lineStyle kind="LINEABOVE" colorName="#000000" start="0,0" stop="0,0"/>
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/>
<lineStyle kind="LINEBEFORE" colorName="#000000" start="1,0" stop="1,-1"/>
<lineStyle kind="LINEABOVE" colorName="#000000" start="1,0" stop="1,0"/>
<lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/>
<lineStyle kind="LINEBEFORE" colorName="#000000" start="2,0" stop="2,-1"/>
<lineStyle kind="LINEABOVE" colorName="#000000" start="2,0" stop="2,0"/>
<lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/>
<lineStyle kind="LINEBEFORE" colorName="#000000" start="3,0" stop="3,-1"/>
<lineStyle kind="LINEAFTER" colorName="#000000" start="3,0" stop="3,-1"/>
<lineStyle kind="LINEABOVE" colorName="#000000" start="3,0" stop="3,0"/>
<lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/>
</blockTableStyle>
<blockTableStyle id="Table2">
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,0" stop="-1,0"/>
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,1" stop="-1,-1"/>
<blockValign value="TOP"/>
<blockAlignment value="RIGHT" start="2,1" stop="-1,-1"/>
</blockTableStyle>
<blockTableStyle id="Table3">
<blockAlignment value="LEFT"/>
<blockValign value="TOP"/>
</blockTableStyle>
<blockTableStyle id="Table4">
<blockAlignment value="LEFT"/>
<blockValign value="TOP"/>
<lineStyle kind="LINEBEFORE" colorName="#000000" start="0,0" stop="0,-1"/>
<lineStyle kind="LINEABOVE" colorName="#000000" start="0,0" stop="0,0"/>
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,-1" stop="0,-1"/>
<lineStyle kind="LINEBEFORE" colorName="#000000" start="1,0" stop="1,-1"/>
<lineStyle kind="LINEABOVE" colorName="#000000" start="1,0" stop="1,0"/>
<lineStyle kind="LINEBELOW" colorName="#000000" start="1,-1" stop="1,-1"/>
<lineStyle kind="LINEBEFORE" colorName="#000000" start="2,0" stop="2,-1"/>
<lineStyle kind="LINEABOVE" colorName="#000000" start="2,0" stop="2,0"/>
<lineStyle kind="LINEBELOW" colorName="#000000" start="2,-1" stop="2,-1"/>
<lineStyle kind="LINEBEFORE" colorName="#000000" start="3,0" stop="3,-1"/>
<lineStyle kind="LINEAFTER" colorName="#000000" start="3,0" stop="3,-1"/>
<lineStyle kind="LINEABOVE" colorName="#000000" start="3,0" stop="3,0"/>
<lineStyle kind="LINEBELOW" colorName="#000000" start="3,-1" stop="3,-1"/>
</blockTableStyle>
<blockTableStyle id="Table7">
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,0" stop="-1,0"/>
</blockTableStyle>
<initialize>
<paraStyle name="all" alignment="justify"/>
</initialize>
<paraStyle name="P1" fontName="Helvetica-Bold" fontSize="14.0" leading="25" alignment="CENTER" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P2" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P2c" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P3" fontName="Helvetica" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P10" fontName="Helvetica" fontSize="8.0" leading="14" spaceBefore="0.0" spaceAfter="6.0" alignment="RIGHT"/>
<paraStyle name="P9" fontName="Helvetica-Bold" alignment="CENTER" fontSize="14.5" leftIndent="-5.0"/>
<paraStyle name="P9b" fontName="Helvetica" fontSize="8" alignment="LEFT"/>
<paraStyle name="P9c" fontName="Helvetica" fontSize="8" alignment="RIGHT"/>
<paraStyle name="P12" fontName="Helvetica" fontSize="8.0" leading="14" alignment="CENTER" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P12a" fontName="Helvetica-Bold" fontSize="8.0" alignment="LEFT" spaceBefore="0.0" spaceAfter="6.0"/>
<paraStyle name="P14" rightIndent="17.0" leftIndent="-0.0" fontName="Helvetica" fontSize="8.0" leading="10" spaceBefore="0.0" spaceAfter="6.0"/>
<blockTableStyle id="TrLevel6">
<blockLeftPadding length="60" start="1,0" stop="1,0"/>
</blockTableStyle>
<blockTableStyle id="TrLevel5">
<blockLeftPadding length="40" start="1,0" stop="1,0"/>
</blockTableStyle>
<blockTableStyle id="TrLevel4">
<blockLeftPadding length="20" start="1,0" stop="1,0"/>
</blockTableStyle>
<blockTableStyle id="TrLevel3">
<blockLeftPadding length="0" start="1,0" stop="1,0"/>
</blockTableStyle>
<blockTableStyle id="TrLevel2">
<blockLeftPadding length="0" start="1,0" stop="1,0"/>
<lineStyle kind="LINEBELOW" colorName="#777777" start="1,0" stop="1,0"/>
<blockTopPadding length="13" start="0,0" stop="-1,0"/>
<blockBottomPadding length="2" start="0,0" stop="-1,0"/>
<blockFont name="Times-Bold" start="0,0" stop="-1,-1"/>
</blockTableStyle>
<blockTableStyle id="TrLevel1">
<lineStyle kind="LINEBELOW" colorName="#000000" start="0,0" stop="-1,0"/>
<blockLeftPadding length="0" start="1,0" stop="1,0"/>
<blockTopPadding length="26" start="0,0" stop="-1,0"/>
<blockBottomPadding length="2" start="0,0" stop="-1,0"/>
<blockFont name="Times-Bold" start="0,0" stop="-1,-1"/>
</blockTableStyle>
<paraStyle
name="Level5"
fontName="Helvetica"
fontSize="8.0" />
<paraStyle
name="Level4"
fontName="Helvetica"
fontSize="8.0" />
<paraStyle
name="Level3"
fontName="Helvetica"
fontSize="8.0" />
<paraStyle
name="Level2"
firstLineIndent="-0.03cm"
fontName="Helvetica-Bold"
fontSize="8.0" />
<paraStyle name="Level1"
fontSize="8.0"
fontName="Helvetica-Bold"
/>
<paraStyle name="Caption" fontName="Helvetica" fontSize="10.0" leading="13" spaceBefore="6.0" spaceAfter="6.0"/>
<paraStyle name="Index" fontName="Helvetica"/>
</stylesheet>
<images/>
<story>
<para style="P2">[[ repeatIn(objects,'o') ]]</para>
<para style="P2">[[ setLang(o.partner_id.lang) ]]</para>
<para style="P1">Maintenance And Support Summary</para>
<para style="P12a"></para>
<para style="P12a"></para>
<blockTable colWidths="258.0,259.0" style="Table1" repeatRows="1">
<tr>
<td>
<para style="P12a">Description: </para>
</td>
<td>
<para style="P2">[[ o.name ]]</para>
</td>
</tr>
<tr>
<td>
<para style="P12a">Report Date: </para>
</td>
<td>
<para style="P2">[[ formatLang(time.strftime(date_format), date=True) ]]</para>
</td>
</tr>
<tr>
<td>
<para style="P12a">Invoice Date: </para>
</td>
<td>
<para style="P2">[[ o.date_invoice and formatLang(o.date_invoice, date=True) or '' ]]</para>
</td>
</tr>
<tr>
<td>
<para style="P12a">Amount bought: [[ (o.type == 'amount' or removeParentNode('para')) and '' ]]</para>
<para style="P12a">Quantity of hours bought: [[ (o.type == 'hours' or removeParentNode('para')) and '' ]]</para>
</td>
<td>
<para style="P2">[[ o.amount_hours_block ]]</para>
</td>
</tr>
<tr>
<td>
<para style="P12a">Amount used: [[ (o.type == 'amount' or removeParentNode('para')) and '' ]]</para>
<para style="P12a">Quantity of hours used: [[ (o.type == 'hours' or removeParentNode('para')) and '' ]]</para>
</td>
<td>
<para style="P2">[[ round(o.amount_hours_block_done, 2) ]]</para>
</td>
</tr>
<tr>
<td>
<para style="P12a">Remaining amount: [[ (o.type == 'amount' or removeParentNode('para')) and '' ]]</para>
<para style="P12a">Remaining hours: [[ (o.type == 'hours' or removeParentNode('para')) and '' ]]</para>
</td>
<td>
<para style="P2">[[ o.amount_hours_block and round(o.amount_hours_block_delta, 2) or '' ]]</para>
</td>
</tr>
</blockTable>
<para style="P12a"></para>
<para style="P12a"></para>
<para style="P12a"></para>
<blockTable colWidths="58,305.0,52.0,52.0,52.0" style="Table2" repeatRows="1">
<tr>
<td>
<para style="P12a">Date</para>
</td>
<td>
<para style="P12a">Description</para>
</td>
<td>
<para style="P12a">Quantity</para>
</td>
<td>
<para style="P12a">Invoicing</para>
</td>
<td>
<para style="P12a">Deduced</para>
</td>
</tr>
<tr>
[[ repeatIn(analytic_lines(o), 'l') ]]
<td>
<para style="P2">[[ l.date if formatLang(l.date, date=True) else '' ]]</para>
</td>
<td>
<para style="P2">[[ l.name or '' ]]</para>
</td>
<td>
<para style="P2c">[[ round(l.unit_amount, 2) or '0.0' ]]</para>
</td>
<td>
<para style="P2c">[[ l.to_invoice.customer_name ]]</para>
</td>
<td>
<para style="P2c">[[ round((l.unit_amount and l.to_invoice) and (l.unit_amount - (l.unit_amount * l.to_invoice.factor) / 100 ), 2) or '0.0' ]]</para>
</td>
</tr>
</blockTable>
</story>
</document>

11
__unported__/analytic_hours_block/security/hours_block_security.xml

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp><data>
<record id="hours_block_comp_rule" model="ir.rule">
<field name="name">Hours Block multi company rule</field>
<field model="ir.model" name="model_id" ref="model_account_hours_block"/>
<field eval="True" name="global"/>
<field name="domain_force">[]</field>
</record>
</data></openerp>

3
__unported__/analytic_hours_block/security/ir.model.access.csv

@ -1,3 +0,0 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_hours_block_user","account.hours.block","model_account_hours_block","base.group_user",1,0,0,0
"access_hours_block_invoice_manager","account.hours.block","model_account_hours_block","account.group_account_invoice",1,1,1,1

5
__unported__/project_sla/__init__.py

@ -1,5 +0,0 @@
# -*- coding: utf-8 -*-
import project_sla
import analytic_account
import project_sla_control
import project_issue

132
__unported__/project_sla/__openerp__.py

@ -1,132 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2013 Daniel Reis
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Service Level Agreements',
'summary': 'Define SLAs for your Contracts',
'version': '1.0',
"category": "Project Management",
'description': """\
Contract SLAs
===============
SLAs are assigned to Contracts, on the Analytic Account form, SLA Definition
separator. This is also where new SLA Definitions are created.
One Contract can have several SLA Definitions attached, allowing for
"composite SLAs". For example, a contract could have a Response Time SLA (time
to start resolution) and a Resolution Time SLA (time to close request).
SLA Controlled Documents
========================
Only Project Issue documents are made SLA controllable.
However, a framework is made available to easily build extensions to make
other documents models SLA controlled.
SLA controlled documents have attached information on the list of SLA rules
they should meet (more than one in the case for composite SLAs) and a summary
SLA status:
* "watching" the service level (it has SLA requirements to meet)
* under "warning" (limit dates are close, special attention is needed)
* "failed" (one on the SLA limits has not been met)
* "achieved" (all SLA limits have been met)
Transient states, such as "watching" and "warning", are regularly updated by
a hourly scheduled job, that reevaluates the warning and limit dates against
the current time and changes the state when find dates that have been exceeded.
To decide what SLA Definitions apply for a specific document, first a lookup
is made for a ``analytic_account_id`` field. If not found, then it will
look up for the ``project_id`` and it's corresponding ``analytic_account_id``.
Specifically, the Service Desk module introduces a Analytic Account field for
Project Issues. This makes it possible for a Service Team (a "Project") to
have a generic SLA, but at the same time allow for some Contracts to have
specific SLAs (such as the case for "premium" service conditions).
SLA Definitions and Rules
=========================
New SLA Definitions are created from the Analytic Account form, SLA Definition
field.
Each definition can have one or more Rules.
The particular rule to use is decided by conditions, so that you can set
different service levels based on request attributes, such as Priority or
Category.
Each rule condition is evaluated in "sequence" order, and the first onea to met
is the one to be used.
In the simplest case, a single rule with no condition is just what is needed.
Each rule sets a number of hours until the "limit date", and the number of
hours until a "warning date". The former will be used to decide if the SLA
was achieved, and the later can be used for automatic alarms or escalation
procedures.
Time will be counted from creation date, until the "Control Date" specified for
the SLA Definition. That would usually be the "Close" (time until resolution)
or the "Open" (time until response) dates.
The working calendar set in the related Project definitions will be used (see
the "Other Info" tab). If none is defined, a builtin "all days, 8-12 13-17"
default calendar is used.
A timezone and leave calendars will also used, based on either the assigned
user (document's `user_id`) or on the current user.
Setup checklist
===============
The basic steps to configure SLAs for a Project are:
* Set Project's Working Calendar, at Project definitions, "Other Info" tab
* Go to the Project's Analytic Account form; create and set SLA Definitions
* Use the "Reapply SLAs" button on the Analytic Account form
* See Project Issue's calculated SLAs in the new "Service Levels" tab
Credits and Contributors
========================
* Daniel Reis (https://launchpad.net/~dreis-pt)
* David Vignoni, author of the icon from the KDE 3.x Nuvola icon theme
""",
'author': 'Daniel Reis',
'website': '',
'depends': [
'project_issue',
],
'data': [
'project_sla_view.xml',
'project_sla_control_view.xml',
'project_sla_control_data.xml',
'analytic_account_view.xml',
'project_view.xml',
'project_issue_view.xml',
'security/ir.model.access.csv',
],
'demo': ['project_sla_demo.xml'],
'test': ['test/project_sla.yml'],
'installable': False,
}

69
__unported__/project_sla/analytic_account.py

@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2013 Daniel Reis
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import fields, orm
class AnalyticAccount(orm.Model):
""" Add SLA to Analytic Accounts """
_inherit = 'account.analytic.account'
_columns = {
'sla_ids': fields.many2many(
'project.sla', string='Service Level Agreement'),
}
def _reapply_sla(self, cr, uid, ids, recalc_closed=False, context=None):
"""
Force SLA recalculation on open documents that already are subject to
this SLA Definition.
To use after changing a Contract SLA or it's Definitions.
The ``recalc_closed`` flag allows to also recompute closed documents.
"""
ctrl_obj = self.pool.get('project.sla.control')
proj_obj = self.pool.get('project.project')
exclude_states = ['cancelled'] + (not recalc_closed and ['done'] or [])
for contract in self.browse(cr, uid, ids, context=context):
# for each contract, and for each model under SLA control ...
for m_name in set([sla.control_model for sla in contract.sla_ids]):
model = self.pool.get(m_name)
doc_ids = []
if 'analytic_account_id' in model._columns:
doc_ids += model.search(
cr, uid,
[('analytic_account_id', '=', contract.id),
('state', 'not in', exclude_states)],
context=context)
if 'project_id' in model._columns:
proj_ids = proj_obj.search(
cr, uid, [('analytic_account_id', '=', contract.id)],
context=context)
doc_ids += model.search(
cr, uid,
[('project_id', 'in', proj_ids),
('state', 'not in', exclude_states)],
context=context)
if doc_ids:
docs = model.browse(cr, uid, doc_ids, context=context)
ctrl_obj.store_sla_control(cr, uid, docs, context=context)
return True
def reapply_sla(self, cr, uid, ids, context=None):
""" Reapply SLAs button action """
return self._reapply_sla(cr, uid, ids, context=context)

24
__unported__/project_sla/analytic_account_view.xml

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_account_analytic_account_form_sla" model="ir.ui.view">
<field name="name">view_account_analytic_account_form_sla</field>
<field name="model">account.analytic.account</field>
<field name="inherit_id" ref="analytic.view_account_analytic_account_form"/>
<field name="arch" type="xml">
<page name="contract_page" position="after">
<page name="sla_page" string="Service Level Agreement">
<field name="sla_ids" nolabel="1"/>
<button name="reapply_sla" string="Reapply" type="object"
help="Reapply the SLAs to all Contract's documents."
groups="project.group_project_manager" />
</page>
</page>
</field>
</record>
</data>
</openerp>

296
__unported__/project_sla/i18n/project_sla.pot

@ -1,296 +0,0 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * project_sla
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 7.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-12-19 10:28+0000\n"
"PO-Revision-Date: 2013-12-19 10:28+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: project_sla
#: model:project.sla,name:project_sla.sla_response
msgid "Standard Response Time"
msgstr ""
#. module: project_sla
#: help:project.sla,control_field_id:0
msgid "Date field used to check if the SLA was achieved."
msgstr ""
#. module: project_sla
#: model:ir.model,name:project_sla.model_project_issue
msgid "Project Issue"
msgstr ""
#. module: project_sla
#: model:ir.model,name:project_sla.model_project_sla_control
msgid "SLA Control Registry"
msgstr ""
#. module: project_sla
#: view:project.sla:0
msgid "Reapply SLA on Contracts"
msgstr ""
#. module: project_sla
#: view:project.project:0
msgid "Administration"
msgstr ""
#. module: project_sla
#: view:project.issue:0
msgid "Priority"
msgstr ""
#. module: project_sla
#: selection:project.issue,sla_state:0
#: selection:project.sla.control,sla_state:0
#: selection:project.sla.controlled,sla_state:0
msgid "Failed"
msgstr ""
#. module: project_sla
#: field:project.sla.control,sla_warn_date:0
msgid "Warning Date"
msgstr ""
#. module: project_sla
#: field:project.sla.line,warn_qty:0
msgid "Hours to Warn"
msgstr ""
#. module: project_sla
#: view:project.sla.control:0
msgid "Service Level"
msgstr ""
#. module: project_sla
#: selection:project.issue,sla_state:0
#: selection:project.sla.control,sla_state:0
#: selection:project.sla.controlled,sla_state:0
msgid "Watching"
msgstr ""
#. module: project_sla
#: view:project.sla:0
#: field:project.sla,analytic_ids:0
msgid "Contracts"
msgstr ""
#. module: project_sla
#: field:project.sla,name:0
#: field:project.sla.line,name:0
msgid "Title"
msgstr ""
#. module: project_sla
#: field:project.sla,active:0
msgid "Active"
msgstr ""
#. module: project_sla
#: field:project.issue,sla_control_ids:0
#: field:project.sla.controlled,sla_control_ids:0
msgid "SLA Control"
msgstr ""
#. module: project_sla
#: field:project.sla.control,sla_achieved:0
msgid "Achieved?"
msgstr ""
#. module: project_sla
#: view:project.issue:0
#: field:project.issue,sla_state:0
#: field:project.sla.control,sla_state:0
#: field:project.sla.controlled,sla_state:0
msgid "SLA Status"
msgstr ""
#. module: project_sla
#: field:project.sla.line,condition:0
msgid "Condition"
msgstr ""
#. module: project_sla
#: field:project.sla,control_model:0
msgid "For documents"
msgstr ""
#. module: project_sla
#: view:project.sla:0
#: field:project.sla.line,sla_id:0
msgid "SLA Definition"
msgstr ""
#. module: project_sla
#: model:project.sla.line,name:project_sla.sla_response_rule2
msgid "Response in two business days"
msgstr ""
#. module: project_sla
#: selection:project.issue,sla_state:0
#: selection:project.sla.control,sla_state:0
#: selection:project.sla.controlled,sla_state:0
msgid "Will Fail"
msgstr ""
#. module: project_sla
#: field:project.sla.control,sla_line_id:0
msgid "Service Agreement"
msgstr ""
#. module: project_sla
#: field:project.sla.control,doc_id:0
msgid "Document ID"
msgstr ""
#. module: project_sla
#: field:project.sla.control,locked:0
msgid "Recalculation disabled"
msgstr ""
#. module: project_sla
#: field:project.sla.control,sla_limit_date:0
msgid "Limit Date"
msgstr ""
#. module: project_sla
#: help:project.sla.line,condition:0
msgid "Apply only if this expression is evaluated to True. The document fields can be accessed using either o, obj or object. Example: obj.priority <= '2'"
msgstr ""
#. module: project_sla
#: model:project.sla.line,name:project_sla.sla_resolution_rule1
msgid "Resolution in two business days"
msgstr ""
#. module: project_sla
#: view:account.analytic.account:0
msgid "Reapply"
msgstr ""
#. module: project_sla
#: field:project.sla.line,limit_qty:0
msgid "Hours to Limit"
msgstr ""
#. module: project_sla
#: field:project.sla,sla_line_ids:0
#: view:project.sla.line:0
msgid "Definitions"
msgstr ""
#. module: project_sla
#: model:project.sla.line,name:project_sla.sla_resolution_rule2
msgid "Resolution in three business days"
msgstr ""
#. module: project_sla
#: field:project.sla.control,sla_close_date:0
msgid "Close Date"
msgstr ""
#. module: project_sla
#: model:ir.model,name:project_sla.model_project_sla
msgid "project.sla"
msgstr ""
#. module: project_sla
#: field:project.sla,control_field_id:0
msgid "Control Date"
msgstr ""
#. module: project_sla
#: model:project.sla.line,name:project_sla.sla_response_rule1
msgid "Response in one business day"
msgstr ""
#. module: project_sla
#: selection:project.issue,sla_state:0
#: selection:project.sla.control,sla_state:0
#: selection:project.sla.controlled,sla_state:0
msgid "Achieved"
msgstr ""
#. module: project_sla
#: view:account.analytic.account:0
#: field:account.analytic.account,sla_ids:0
msgid "Service Level Agreement"
msgstr ""
#. module: project_sla
#: model:project.sla,name:project_sla.sla_resolution
msgid "Standard Resolution Time"
msgstr ""
#. module: project_sla
#: field:project.sla.line,sequence:0
msgid "Sequence"
msgstr ""
#. module: project_sla
#: view:project.issue:0
msgid "Service Levels"
msgstr ""
#. module: project_sla
#: model:ir.model,name:project_sla.model_account_analytic_account
msgid "Analytic Account"
msgstr ""
#. module: project_sla
#: view:project.sla:0
msgid "Rules"
msgstr ""
#. module: project_sla
#: help:project.sla.control,locked:0
msgid "Safeguard manual changes from future automatic recomputations."
msgstr ""
#. module: project_sla
#: selection:project.issue,sla_state:0
#: selection:project.sla.control,sla_state:0
#: selection:project.sla.controlled,sla_state:0
msgid "Warning"
msgstr ""
#. module: project_sla
#: view:project.issue:0
msgid "Extra Info"
msgstr ""
#. module: project_sla
#: field:project.sla.control,doc_model:0
msgid "Document Model"
msgstr ""
#. module: project_sla
#: model:ir.model,name:project_sla.model_project_sla_line
msgid "project.sla.line"
msgstr ""
#. module: project_sla
#: view:account.analytic.account:0
msgid "Reapply the SLAs to all Contract's documents."
msgstr ""
#. module: project_sla
#: field:project.sla.control,sla_start_date:0
msgid "Start Date"
msgstr ""
#. module: project_sla
#: model:ir.model,name:project_sla.model_project_sla_controlled
msgid "SLA Controlled Document"
msgstr ""

BIN
__unported__/project_sla/images/10_sla_contract.png

Before

Width: 773  |  Height: 464  |  Size: 18 KiB

BIN
__unported__/project_sla/images/20_sla_definition.png

Before

Width: 915  |  Height: 437  |  Size: 19 KiB

BIN
__unported__/project_sla/images/30_sla_controlled.png

Before

Width: 873  |  Height: 572  |  Size: 26 KiB

75
__unported__/project_sla/m2m.py

@ -1,75 +0,0 @@
"""
Wrapper for OpenERP's cryptic write conventions for x2many fields.
Example usage:
import m2m
browse_rec.write({'many_ids: m2m.clear())
browse_rec.write({'many_ids: m2m.link(99))
browse_rec.write({'many_ids: m2m.add({'name': 'Monty'}))
browse_rec.write({'many_ids: m2m.replace([98, 99]))
Since returned values are lists, the can be joined using the plus operator:
browse_rec.write({'many_ids: m2m.clear() + m2m.link(99))
(Source: https://github.com/dreispt/openerp-write2many)
"""
def create(values):
""" Create a referenced record """
assert isinstance(values, dict)
return [(0, 0, values)]
def add(values):
""" Intuitive alias for create() """
return create(values)
def write(id, values):
""" Write on referenced record """
assert isinstance(id, int)
assert isinstance(values, dict)
return [(1, id, values)]
def remove(id):
""" Unlink and delete referenced record """
assert isinstance(id, int)
return [(2, id)]
def unlink(id):
""" Unlink but do not delete the referenced record """
assert isinstance(id, int)
return [(3, id)]
def link(id):
""" Link but do not delete the referenced record """
assert isinstance(id, int)
return [(4, id)]
def clear():
""" Unlink all referenced records (doesn't delete them) """
return [(5, 0)]
def replace(ids):
""" Unlink all current records and replace them with a new list """
assert isinstance(ids, list)
return [(6, 0, ids)]
if __name__ == "__main__":
# Tests:
assert create({'name': 'Monty'}) == [(0, 0, {'name': 'Monty'})]
assert write(99, {'name': 'Monty'}) == [(1, 99, {'name': 'Monty'})]
assert remove(99) == [(2, 99)]
assert unlink(99) == [(3, 99)]
assert clear() == [(5, 0)]
assert replace([97, 98, 99]) == [(6, 0, [97, 98, 99])]
print("Done!")

29
__unported__/project_sla/project_issue.py

@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2013 Daniel Reis
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm
class ProjectIssue(orm.Model):
"""
Extend Project Issues to be SLA Controlled
"""
_name = 'project.issue'
_inherit = ['project.issue', 'project.sla.controlled']

60
__unported__/project_sla/project_issue_view.xml

@ -1,60 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Project Issue Form -->
<record id="project_issue_form_view_sla" model="ir.ui.view">
<field name="name">project_issue_form_view_sla</field>
<field name="model">project.issue</field>
<field name="inherit_id" ref="project_issue.project_issue_form_view"/>
<field name="arch" type="xml">
<page string="Extra Info" position="after">
<page name="sla_page" string="Service Levels"
attrs="{'invisible': [('sla_state', '=', False)]}">
<group>
<group>
<field name="sla_state" />
</group>
<group>
<field name="write_date" />
</group>
</group>
<field name="sla_control_ids"/>
</page>
</page>
</field>
</record>
<!-- Project Issue List -->
<record model="ir.ui.view" id="project_issue_tree_view_sla">
<field name="name">project_issue_tree_view_sla</field>
<field name="model">project.issue</field>
<field name="inherit_id" ref="project_issue.project_issue_tree_view"/>
<field name="arch" type="xml">
<field name="project_id" position="after">
<field name="sla_state"/>
</field>
</field>
</record>
<!-- Project Issue Filter -->
<record id="view_project_issue_filter_sdesk" model="ir.ui.view">
<field name="name">view_project_issue_filter_sdesk</field>
<field name="model">project.issue</field>
<field name="inherit_id" ref="project_issue.view_project_issue_filter"/>
<field name="arch" type="xml">
<filter string="Priority" position="after">
<filter string="SLA Status" context="{'group_by':'sla_state'}" />
</filter>
</field>
</record>
</data>
</openerp>

86
__unported__/project_sla/project_sla.py

@ -1,86 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2013 Daniel Reis
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import fields, orm
class SLADefinition(orm.Model):
"""
SLA Definition
"""
_name = 'project.sla'
_description = 'SLA Definition'
_columns = {
'name': fields.char('Title', size=64, required=True, translate=True),
'active': fields.boolean('Active'),
'control_model': fields.char('For documents', size=128, required=True),
'control_field_id': fields.many2one(
'ir.model.fields', 'Control Date', required=True,
domain="[('model_id.model', '=', control_model),"
" ('ttype', 'in', ['date', 'datetime'])]",
help="Date field used to check if the SLA was achieved."),
'sla_line_ids': fields.one2many(
'project.sla.line', 'sla_id', 'Definitions'),
'analytic_ids': fields.many2many(
'account.analytic.account', string='Contracts'),
}
_defaults = {
'active': True,
}
def _reapply_slas(self, cr, uid, ids, recalc_closed=False, context=None):
"""
Force SLA recalculation on all _open_ Contracts for the selected SLAs.
To use upon SLA Definition modifications.
"""
contract_obj = self.pool.get('account.analytic.account')
for sla in self.browse(cr, uid, ids, context=context):
contr_ids = [x.id for x in sla.analytic_ids if x.state == 'open']
contract_obj._reapply_sla(
cr, uid, contr_ids, recalc_closed=recalc_closed,
context=context)
return True
def reapply_slas(self, cr, uid, ids, context=None):
""" Reapply SLAs button action """
return self._reapply_slas(cr, uid, ids, context=context)
class SLARules(orm.Model):
"""
SLA Definition Rule Lines
"""
_name = 'project.sla.line'
_definition = 'SLA Definition Rule Lines'
_order = 'sla_id,sequence'
_columns = {
'sla_id': fields.many2one('project.sla', 'SLA Definition'),
'sequence': fields.integer('Sequence'),
'name': fields.char('Title', size=64, required=True, translate=True),
'condition': fields.char(
'Condition', size=256, help="Apply only if this expression is "
"evaluated to True. The document fields can be accessed using "
"either o, obj or object. Example: obj.priority <= '2'"),
'limit_qty': fields.integer('Hours to Limit'),
'warn_qty': fields.integer('Hours to Warn'),
}
_defaults = {
'sequence': 10,
}

322
__unported__/project_sla/project_sla_control.py

@ -1,322 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2013 Daniel Reis
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import fields, orm
from openerp.tools.safe_eval import safe_eval
from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT as DT_FMT
from openerp import SUPERUSER_ID
from datetime import timedelta
from datetime import datetime as dt
import m2m
import logging
_logger = logging.getLogger(__name__)
SLA_STATES = [('5', 'Failed'), ('4', 'Will Fail'), ('3', 'Warning'),
('2', 'Watching'), ('1', 'Achieved')]
def safe_getattr(obj, dotattr, default=False):
"""
Follow an object attribute dot-notation chain to find the leaf value.
If any attribute doesn't exist or has no value, just return False.
Checks hasattr ahead, to avoid ORM Browse log warnings.
"""
attrs = dotattr.split('.')
while attrs:
attr = attrs.pop(0)
if attr in obj._model._columns:
try:
obj = getattr(obj, attr)
except AttributeError:
return default
if not obj:
return default
else:
return default
return obj
class SLAControl(orm.Model):
"""
SLA Control Registry
Each controlled document (Issue, Claim, ...) will have a record here.
This model concentrates all the logic for Service Level calculation.
"""
_name = 'project.sla.control'
_description = 'SLA Control Registry'
_columns = {
'doc_id': fields.integer('Document ID', readonly=True),
'doc_model': fields.char('Document Model', size=128, readonly=True),
'sla_line_id': fields.many2one(
'project.sla.line', 'Service Agreement'),
'sla_warn_date': fields.datetime('Warning Date'),
'sla_limit_date': fields.datetime('Limit Date'),
'sla_start_date': fields.datetime('Start Date'),
'sla_close_date': fields.datetime('Close Date'),
'sla_achieved': fields.integer('Achieved?'),
'sla_state': fields.selection(SLA_STATES, string="SLA Status"),
'locked': fields.boolean(
'Recalculation disabled',
help="Safeguard manual changes from future automatic "
"recomputations."),
# Future: perfect SLA manual handling
}
def write(self, cr, uid, ids, vals, context=None):
"""
Update the related Document's SLA State when any of the SLA Control
lines changes state
"""
res = super(SLAControl, self).write(
cr, uid, ids, vals, context=context)
new_state = vals.get('sla_state')
if new_state:
# just update sla_state without recomputing the whole thing
context = context or {}
context['__sla_stored__'] = 1
for sla in self.browse(cr, uid, ids, context=context):
doc = self.pool.get(sla.doc_model).browse(
cr, uid, sla.doc_id, context=context)
if doc.sla_state < new_state:
doc.write({'sla_state': new_state})
return res
def update_sla_states(self, cr, uid, context=None):
"""
Updates SLA States, given the current datetime:
Only works on "open" sla states (watching, warning and will fail):
- exceeded limit date are set to "will fail"
- exceeded warning dates are set to "warning"
To be used by a scheduled job.
"""
now = dt.now().strftime(DT_FMT)
# SLAs to mark as "will fail"
control_ids = self.search(
cr, uid,
[('sla_state', 'in', ['2', '3']), ('sla_limit_date', '<', now)],
context=context)
self.write(cr, uid, control_ids, {'sla_state': '4'}, context=context)
# SLAs to mark as "warning"
control_ids = self.search(
cr, uid,
[('sla_state', 'in', ['2']), ('sla_warn_date', '<', now)],
context=context)
self.write(cr, uid, control_ids, {'sla_state': '3'}, context=context)
return True
def _compute_sla_date(self, cr, uid, working_hours, res_uid,
start_date, hours, context=None):
"""
Return a limit datetime by adding hours to a start_date, honoring
a working_time calendar and a resource's (res_uid) timezone and
availability (leaves)
Currently implemented using a binary search using
_interval_hours_get() from resource.calendar. This is
resource.calendar agnostic, but could be more efficient if
implemented based on it's logic.
Known issue: the end date can be a non-working time; it would be
best for it to be the latest working time possible. Example:
if working time is 08:00 - 16:00 and start_date is 19:00, the +8h
end date will be 19:00 of the next day, and it should rather be
16:00 of the next day.
"""
assert isinstance(start_date, dt)
assert isinstance(hours, int) and hours >= 0
cal_obj = self.pool.get('resource.calendar')
target, step = hours * 3600, 16 * 3600
lo, hi = start_date, start_date
while target > 0 and step > 60:
hi = lo + timedelta(seconds=step)
check = int(3600 * cal_obj._interval_hours_get(
cr, uid, working_hours, lo, hi,
timezone_from_uid=res_uid, exclude_leaves=False,
context=context))
if check <= target:
target -= check
lo = hi
else:
step = int(step / 4.0)
return hi
def _get_computed_slas(self, cr, uid, doc, context=None):
"""
Returns a dict with the computed data for SLAs, given a browse record
for the target document.
* The SLA used is either from a related analytic_account_id or
project_id, whatever is found first.
* The work calendar is taken from the Project's definitions ("Other
Info" tab -> Working Time).
* The timezone used for the working time calculations are from the
document's responsible User (user_id) or from the current User (uid).
For the SLA Achieved calculation:
* Creation date is used to start counting time
* Control date, used to calculate SLA achievement, is defined in the
SLA Definition rules.
"""
def datetime2str(dt_value, fmt): # tolerant datetime to string
return dt_value and dt.strftime(dt_value, fmt) or None
res = []
sla_ids = (safe_getattr(doc, 'analytic_account_id.sla_ids') or
safe_getattr(doc, 'project_id.analytic_account_id.sla_ids'))
if not sla_ids:
return res
for sla in sla_ids:
if sla.control_model != doc._table_name:
continue # SLA not for this model; skip
for l in sla.sla_line_ids:
eval_context = {'o': doc, 'obj': doc, 'object': doc}
if not l.condition or safe_eval(l.condition, eval_context):
start_date = dt.strptime(doc.create_date, DT_FMT)
res_uid = doc.user_id.id or uid
cal = safe_getattr(
doc, 'project_id.resource_calendar_id.id')
warn_date = self._compute_sla_date(
cr, uid, cal, res_uid, start_date, l.warn_qty,
context=context)
lim_date = self._compute_sla_date(
cr, uid, cal, res_uid, warn_date,
l.limit_qty - l.warn_qty,
context=context)
# evaluate sla state
control_val = getattr(doc, sla.control_field_id.name)
if control_val:
control_date = dt.strptime(control_val, DT_FMT)
if control_date > lim_date:
sla_val, sla_state = 0, '5' # failed
else:
sla_val, sla_state = 1, '1' # achieved
else:
control_date = None
now = dt.now()
if now > lim_date:
sla_val, sla_state = 0, '4' # will fail
elif now > warn_date:
sla_val, sla_state = 0, '3' # warning
else:
sla_val, sla_state = 0, '2' # watching
res.append(
{'sla_line_id': l.id,
'sla_achieved': sla_val,
'sla_state': sla_state,
'sla_warn_date': datetime2str(warn_date, DT_FMT),
'sla_limit_date': datetime2str(lim_date, DT_FMT),
'sla_start_date': datetime2str(start_date, DT_FMT),
'sla_close_date': datetime2str(control_date, DT_FMT),
'doc_id': doc.id,
'doc_model': sla.control_model})
break
if sla_ids and not res:
_logger.warning("No valid SLA rule found for %d, SLA Ids %s"
% (doc.id, repr([x.id for x in sla_ids])))
return res
def store_sla_control(self, cr, uid, docs, context=None):
"""
Used by controlled documents to ask for SLA calculation and storage.
``docs`` is a Browse object
"""
# context flag to avoid infinite loops on further writes
context = context or {}
if '__sla_stored__' in context:
return False
else:
context['__sla_stored__'] = 1
res = []
for ix, doc in enumerate(docs):
if ix and ix % 50 == 0:
_logger.info('...%d SLAs recomputed for %s' % (ix, doc._name))
control = {x.sla_line_id.id: x
for x in doc.sla_control_ids}
sla_recs = self._get_computed_slas(cr, uid, doc, context=context)
# calc sla control lines
if sla_recs:
slas = []
for sla_rec in sla_recs:
sla_line_id = sla_rec.get('sla_line_id')
if sla_line_id in control:
control_rec = control.get(sla_line_id)
if not control_rec.locked:
slas += m2m.write(control_rec.id, sla_rec)
else:
slas += m2m.add(sla_rec)
else:
slas = m2m.clear()
# calc sla control summary
vals = {'sla_state': None, 'sla_control_ids': slas}
if sla_recs and doc.sla_control_ids:
vals['sla_state'] = max(
x.sla_state for x in doc.sla_control_ids)
# store sla
doc._model.write( # regular users can't write on SLA Control
cr, SUPERUSER_ID, [doc.id], vals, context=context)
return res
class SLAControlled(orm.AbstractModel):
"""
SLA Controlled documents: AbstractModel to apply SLA control on Models
"""
_name = 'project.sla.controlled'
_description = 'SLA Controlled Document'
_columns = {
'sla_control_ids': fields.many2many(
'project.sla.control', string="SLA Control", ondelete='cascade'),
'sla_state': fields.selection(
SLA_STATES, string="SLA Status", readonly=True),
}
def create(self, cr, uid, vals, context=None):
res = super(SLAControlled, self).create(cr, uid, vals, context=context)
docs = self.browse(cr, uid, [res], context=context)
self.pool.get('project.sla.control').store_sla_control(
cr, uid, docs, context=context)
return res
def write(self, cr, uid, ids, vals, context=None):
res = super(SLAControlled, self).write(
cr, uid, ids, vals, context=context)
docs = [x for x in self.browse(cr, uid, ids, context=context)
if (x.state != 'cancelled') and
(x.state != 'done' or x.sla_state not in ['1', '5'])]
self.pool.get('project.sla.control').store_sla_control(
cr, uid, docs, context=context)
return res
def unlink(self, cr, uid, ids, context=None):
# Unlink and delete all related Control records
for doc in self.browse(cr, uid, ids, context=context):
vals = [m2m.remove(x.id)[0] for x in doc.sla_control_ids]
doc.write({'sla_control_ids': vals})
return super(SLAControlled, self).unlink(cr, uid, ids, context=context)

18
__unported__/project_sla/project_sla_control_data.xml

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="ir_cron_sla_action" model="ir.cron">
<field name="name">Update SLA States</field>
<field name="priority" eval="100"/>
<field name="interval_number">1</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="model">project.sla.control</field>
<field name="function">update_sla_states</field>
<field name="args">()</field>
</record>
</data>
</openerp>

25
__unported__/project_sla/project_sla_control_view.xml

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- List view used when the sla_control_ids field
is added to controlled document's form -->
<record id="view_sla_control_tree" model="ir.ui.view">
<field name="name">view_sla_control_tree</field>
<field name="model">project.sla.control</field>
<field name="arch" type="xml">
<tree string="Service Level">
<field name="sla_line_id"/>
<field name="sla_state"/>
<field name="sla_start_date"/>
<field name="sla_warn_date"/>
<field name="sla_limit_date"/>
<field name="sla_close_date"/>
</tree>
</field>
</record>
</data>
</openerp>

138
__unported__/project_sla/project_sla_demo.xml

@ -1,138 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Working Time calendar -->
<record id="worktime_9_18" model="resource.calendar">
<field name="name">Working Days 09-13 14-18</field>
</record>
<record id="worktime 9_18_0M" model="resource.calendar.attendance">
<field name="dayofweek">0</field>
<field name="name">Monday Morning</field>
<field name="hour_from">9</field>
<field name="hour_to">13</field>
<field name="calendar_id" ref="worktime_9_18" />
</record>
<record id="worktime 9_18_0A" model="resource.calendar.attendance">
<field name="dayofweek">0</field>
<field name="name">Monday Afternoon</field>
<field name="hour_from">14</field>
<field name="hour_to">18</field>
<field name="calendar_id" ref="worktime_9_18" />
</record>
<record id="worktime 9_18_1M" model="resource.calendar.attendance">
<field name="dayofweek">1</field>
<field name="name">Tuesday Morning</field>
<field name="hour_from">9</field>
<field name="hour_to">13</field>
<field name="calendar_id" ref="worktime_9_18" />
</record>
<record id="worktime 9_18_1A" model="resource.calendar.attendance">
<field name="dayofweek">1</field>
<field name="name">Tuesday Afternoon</field>
<field name="hour_from">14</field>
<field name="hour_to">18</field>
<field name="calendar_id" ref="worktime_9_18" />
</record>
<record id="worktime 9_18_2M" model="resource.calendar.attendance">
<field name="dayofweek">2</field>
<field name="name">Wednesday Morning</field>
<field name="hour_from">9</field>
<field name="hour_to">13</field>
<field name="calendar_id" ref="worktime_9_18" />
</record>
<record id="worktime 9_18_2A" model="resource.calendar.attendance">
<field name="dayofweek">2</field>
<field name="name">Wednesday Afternoon</field>
<field name="hour_from">14</field>
<field name="hour_to">18</field>
<field name="calendar_id" ref="worktime_9_18" />
</record>
<record id="worktime 9_18_3M" model="resource.calendar.attendance">
<field name="dayofweek">3</field>
<field name="name">Thursday Morning</field>
<field name="hour_from">9</field>
<field name="hour_to">13</field>
<field name="calendar_id" ref="worktime_9_18" />
</record>
<record id="worktime 9_18_3A" model="resource.calendar.attendance">
<field name="dayofweek">3</field>
<field name="name">Thursday Afternoon</field>
<field name="hour_from">14</field>
<field name="hour_to">18</field>
<field name="calendar_id" ref="worktime_9_18" />
</record>
<record id="worktime 9_18_4M" model="resource.calendar.attendance">
<field name="dayofweek">4</field>
<field name="name">Friday Morning</field>
<field name="hour_from">9</field>
<field name="hour_to">13</field>
<field name="calendar_id" ref="worktime_9_18" />
</record>
<record id="worktime 9_18_4A" model="resource.calendar.attendance">
<field name="dayofweek">4</field>
<field name="name">Friday Afternoon</field>
<field name="hour_from">14</field>
<field name="hour_to">18</field>
<field name="calendar_id" ref="worktime_9_18" />
</record>
<!-- Set Project Calendar -->
<record id="project.project_project_1" model="project.project">
<field name="resource_calendar_id" ref="worktime_9_18" />
</record>
<!-- SLA Definition and Rules -->
<record id="sla_resolution" model="project.sla">
<field name="name">Standard Resolution Time</field>
<field name="control_model">project.issue</field>
<field name="control_field_id"
ref="project_issue.field_project_issue_date_closed"/>
</record>
<record id="sla_resolution_rule1" model="project.sla.line">
<field name="sla_id" ref="sla_resolution"/>
<field name="sequence">10</field>
<field name="name">Resolution in two business days</field>
<field name="condition">obj.priority &lt;= '2'</field>
<field name="limit_qty">16</field>
<field name="warn_qty">8</field>
</record>
<record id="sla_resolution_rule2" model="project.sla.line">
<field name="sla_id" ref="sla_resolution"/>
<field name="sequence">20</field>
<field name="name">Resolution in three business days</field>
<field name="condition"></field>
<field name="limit_qty">24</field>
<field name="warn_qty">16</field>
</record>
<record id="sla_response" model="project.sla">
<field name="name">Standard Response Time</field>
<field name="control_model">project.issue</field>
<field name="control_field_id"
ref="project_issue.field_project_issue_date_open"/>
</record>
<record id="sla_response_rule1" model="project.sla.line">
<field name="sla_id" ref="sla_response"/>
<field name="sequence">10</field>
<field name="name">Response in one business day</field>
<field name="condition">obj.priority &lt;= '2'</field>
<field name="limit_qty">8</field>
<field name="warn_qty">4</field>
</record>
<record id="sla_response_rule2" model="project.sla.line">
<field name="sla_id" ref="sla_response"/>
<field name="sequence">20</field>
<field name="name">Response in two business days</field>
<field name="condition"></field>
<field name="limit_qty">16</field>
<field name="warn_qty">8</field>
</record>
<!-- Set Contract Resolution SLA Definition -->
<record id="project.project_project_1_account_analytic_account" model="account.analytic.account">
<field name="sla_ids" eval="[(6, 0, [ref('sla_resolution')])]" />
</record>
</data>
</openerp>

48
__unported__/project_sla/project_sla_view.xml

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_sla_lines_tree" model="ir.ui.view">
<field name="name">view_sla_lines_tree</field>
<field name="model">project.sla.line</field>
<field name="arch" type="xml">
<tree string="Definitions">
<field name="sequence"/>
<field name="name"/>
<field name="condition"/>
<field name="limit_qty"/>
<field name="warn_qty"/>
</tree>
</field>
</record>
<record id="view_sla_form" model="ir.ui.view">
<field name="name">view_sla_form</field>
<field name="model">project.sla</field>
<field name="arch" type="xml">
<form string="SLA Definition">
<field name="name"/>
<field name="active"/>
<field name="control_model"/>
<field name="control_field_id"/>
<notebook colspan="4">
<page string="Rules" name="rules_page">
<field name="sla_line_ids" nolabel="1"/>
</page>
<page string="Contracts" name="contracts_page">
<field name="analytic_ids" nolabel="1" />
</page>
</notebook>
<button name="reapply_slas" colspan="2"
string="Reapply SLA on Contracts"
type="object" />
</form>
</field>
</record>
</data>
</openerp>

20
__unported__/project_sla/project_view.xml

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="edit_project_sla" model="ir.ui.view">
<field name="name">edit_project_sla</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.edit_project"/>
<field name="arch" type="xml">
<!-- make resource calendar always visible -->
<group string="Administration" position="attributes">
<attribute name="groups"/>
</group>
</field>
</record>
</data>
</openerp>

8
__unported__/project_sla/security/ir.model.access.csv

@ -1,8 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sla_manager,access_sla_manager,model_project_sla,project.group_project_manager,1,1,1,1
access_sla_user,access_sla_user,model_project_sla,base.group_user,1,0,0,0
access_sla_lines_manager,access_sla_lines_manager,model_project_sla_line,project.group_project_manager,1,1,1,1
access_sla_lines_user,access_sla_lines_user,model_project_sla_line,base.group_user,1,0,0,0
access_sla_control_manager,access_sla_control_manager,model_project_sla_control,project.group_project_manager,1,1,0,0
access_sla_control_user,access_sla_control_user,model_project_sla_control,base.group_user,1,0,0,0
access_sla_controlled_manager,access_sla_controlled_manager,model_project_sla_controlled,project.group_project_manager,1,1,1,1

BIN
__unported__/project_sla/static/src/img/icon.png

Before

Width: 128  |  Height: 128  |  Size: 9.3 KiB

66
__unported__/project_sla/test/project_sla.yml

@ -1,66 +0,0 @@
-
Cleanup previous test run
-
!python {model: project.issue}: |
res = self.search(cr, uid, [('name', '=', 'My monitor is flickering')])
self.unlink(cr, uid, res)
-
Create a new Issue
-
!record {model: project.issue, id: issue1, view: False}:
name: "My monitor is flickering"
project_id: project.project_project_1
priority: "3"
user_id: base.user_root
partner_id: base.res_partner_2
email_from: agr@agrolait.com
categ_ids:
- project_issue.project_issue_category_01
-
Close the Issue
-
!python {model: project.issue}: |
self.case_close(cr, uid, [ref("issue1")])
-
Force the Issue's Create Date and Close Date
Created friday before opening hour, closed on next monday near closing hour
-
!python {model: project.issue}: |
import time
self.write(cr, uid, [ref("issue1"),], {
'create_date': time.strftime('2013-11-22 06:15:00'),
'date_closed': time.strftime('2013-11-25 16:45:00'),
})
-
There should be Service Level info generated on the Issue
-
!assert {model: project.issue, id: issue1, string: Issue should have calculated service levels}:
- len(sla_control_ids) == 2
-
Assign an additional "Response SLA" to the Contract
-
!python {model: account.analytic.account}: |
self.write(cr, uid, [ref('project.project_project_1_account_analytic_account')],
{'sla_ids': [(4, ref('sla_response'))]})
-
Button to Reapply the SLA Definition
-
!python {model: project.sla}: |
self._reapply_slas(cr, uid, [ref('sla_resolution')], recalc_closed=True)
-
There should be two Service Level lines generated on the Issue
-
!assert {model: project.issue, id: issue1, string: Issue should have two calculated service levels}:
- len(sla_control_ids) == 2
-
The Issue's Resolution SLA should be "3 business days"
-
!python {model: project.issue}: |
issue = self.browse(cr, uid, ref('issue1'))
for x in issue.sla_control_ids:
print x.sla_line_id.name
if x.sla_line_id.id == ref("sla_resolution_rule2"):
assert x.sla_achieved == 1, "Issue resolution SLA should be achieved"
break
else:
assert False, 'Issue Resolution SLA should be "3 business days"'
Loading…
Cancel
Save